From 00a09d57eb8a041e6a6b0470c53533719c049bab Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 23 Jun 2015 06:53:58 -0400 Subject: [PATCH 001/539] introduce "extensions" form of core.repositoryformatversion Normally we try to avoid bumps of the whole-repository core.repositoryformatversion field. However, it is unavoidable if we want to safely change certain aspects of git in a backwards-incompatible way (e.g., modifying the set of ref tips that we must traverse to generate a list of unreachable, safe-to-prune objects). If we were to bump the repository version for every such change, then any implementation understanding version `X` would also have to understand `X-1`, `X-2`, and so forth, even though the incompatibilities may be in orthogonal parts of the system, and there is otherwise no reason we cannot implement one without the other (or more importantly, that the user cannot choose to use one feature without the other, weighing the tradeoff in compatibility only for that particular feature). This patch documents the existing repositoryformatversion strategy and introduces a new format, "1", which lets a repository specify that it must run with an arbitrary set of extensions. This can be used, for example: - to inform git that the objects should not be pruned based only on the reachability of the ref tips (e.g, because it has "clone --shared" children) - that the refs are stored in a format besides the usual "refs" and "packed-refs" directories Because we bump to format "1", and because format "1" requires that a running git knows about any extensions mentioned, we know that older versions of the code will not do something dangerous when confronted with these new formats. For example, if the user chooses to use database storage for refs, they may set the "extensions.refbackend" config to "db". Older versions of git will not understand format "1" and bail. Versions of git which understand "1" but do not know about "refbackend", or which know about "refbackend" but not about the "db" backend, will refuse to run. This is annoying, of course, but much better than the alternative of claiming that there are no refs in the repository, or writing to a location that other implementations will not read. Note that we are only defining the rules for format 1 here. We do not ever write format 1 ourselves; it is a tool that is meant to be used by users and future extensions to provide safety with older implementations. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- .../technical/repository-version.txt | 81 +++++++++++++++++++ cache.h | 6 ++ setup.c | 37 ++++++++- t/t1302-repo-version.sh | 38 +++++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 Documentation/technical/repository-version.txt diff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt new file mode 100644 index 00000000000000..3d7106d93d6ffa --- /dev/null +++ b/Documentation/technical/repository-version.txt @@ -0,0 +1,81 @@ +Git Repository Format Versions +============================== + +Every git repository is marked with a numeric version in the +`core.repositoryformatversion` key of its `config` file. This version +specifies the rules for operating on the on-disk repository data. An +implementation of git which does not understand a particular version +advertised by an on-disk repository MUST NOT operate on that repository; +doing so risks not only producing wrong results, but actually losing +data. + +Because of this rule, version bumps should be kept to an absolute +minimum. Instead, we generally prefer these strategies: + + - bumping format version numbers of individual data files (e.g., + index, packfiles, etc). This restricts the incompatibilities only to + those files. + + - introducing new data that gracefully degrades when used by older + clients (e.g., pack bitmap files are ignored by older clients, which + simply do not take advantage of the optimization they provide). + +A whole-repository format version bump should only be part of a change +that cannot be independently versioned. For instance, if one were to +change the reachability rules for objects, or the rules for locking +refs, that would require a bump of the repository format version. + +Note that this applies only to accessing the repository's disk contents +directly. An older client which understands only format `0` may still +connect via `git://` to a repository using format `1`, as long as the +server process understands format `1`. + +The preferred strategy for rolling out a version bump (whether whole +repository or for a single file) is to teach git to read the new format, +and allow writing the new format with a config switch or command line +option (for experimentation or for those who do not care about backwards +compatibility with older gits). Then after a long period to allow the +reading capability to become common, we may switch to writing the new +format by default. + +The currently defined format versions are: + +Version `0` +----------- + +This is the format defined by the initial version of git, including but +not limited to the format of the repository directory, the repository +configuration file, and the object and ref storage. Specifying the +complete behavior of git is beyond the scope of this document. + +Version `1` +----------- + +This format is identical to version `0`, with the following exceptions: + + 1. When reading the `core.repositoryformatversion` variable, a git + implementation which supports version 1 MUST also read any + configuration keys found in the `extensions` section of the + configuration file. + + 2. If a version-1 repository specifies any `extensions.*` keys that + the running git has not implemented, the operation MUST NOT + proceed. Similarly, if the value of any known key is not understood + by the implementation, the operation MUST NOT proceed. + +Note that if no extensions are specified in the config file, then +`core.repositoryformatversion` SHOULD be set to `0` (setting it to `1` +provides no benefit, and makes the repository incompatible with older +implementations of git). + +This document will serve as the master list for extensions. Any +implementation wishing to define a new extension should make a note of +it here, in order to claim the name. + +The defined extensions are: + +`noop` +~~~~~~ + +This extension does not change git's behavior at all. It is useful only +for testing format-1 compatibility. diff --git a/cache.h b/cache.h index 4f554664c5bd06..996584c1ceaced 100644 --- a/cache.h +++ b/cache.h @@ -686,7 +686,13 @@ extern char *notes_ref_name; extern int grafts_replace_parents; +/* + * GIT_REPO_VERSION is the version we write by default. The + * _READ variant is the highest number we know how to + * handle. + */ #define GIT_REPO_VERSION 0 +#define GIT_REPO_VERSION_READ 1 extern int repository_format_version; extern int check_repository_format(void); diff --git a/setup.c b/setup.c index 82c0cc2a13bfea..0d5384683ce2e4 100644 --- a/setup.c +++ b/setup.c @@ -5,6 +5,7 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; static int work_tree_config_is_bogus; +static struct string_list unknown_extensions = STRING_LIST_INIT_DUP; /* * The input parameter must contain an absolute path, and it must already be @@ -352,10 +353,23 @@ void setup_work_tree(void) static int check_repo_format(const char *var, const char *value, void *cb) { + const char *ext; + if (strcmp(var, "core.repositoryformatversion") == 0) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); + else if (skip_prefix(var, "extensions.", &ext)) { + /* + * record any known extensions here; otherwise, + * we fall through to recording it as unknown, and + * check_repository_format will complain + */ + if (!strcmp(ext, "noop")) + ; + else + string_list_append(&unknown_extensions, ext); + } return 0; } @@ -366,6 +380,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) config_fn_t fn; int ret = 0; + string_list_clear(&unknown_extensions, 0); + if (get_common_dir(&sb, gitdir)) fn = check_repo_format; else @@ -383,16 +399,31 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) * is a good one. */ git_config_early(fn, NULL, repo_config); - if (GIT_REPO_VERSION < repository_format_version) { + if (GIT_REPO_VERSION_READ < repository_format_version) { if (!nongit_ok) die ("Expected git repo version <= %d, found %d", - GIT_REPO_VERSION, repository_format_version); + GIT_REPO_VERSION_READ, repository_format_version); warning("Expected git repo version <= %d, found %d", - GIT_REPO_VERSION, repository_format_version); + GIT_REPO_VERSION_READ, repository_format_version); warning("Please upgrade Git"); *nongit_ok = -1; ret = -1; } + + if (repository_format_version >= 1 && unknown_extensions.nr) { + int i; + + if (!nongit_ok) + die("unknown repository extension: %s", + unknown_extensions.items[0].string); + + for (i = 0; i < unknown_extensions.nr; i++) + warning("unknown repository extension: %s", + unknown_extensions.items[i].string); + *nongit_ok = -1; + ret = -1; + } + strbuf_release(&sb); return ret; } diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 0d9388afc4e20a..8dd6fd7baa6129 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -67,4 +67,42 @@ test_expect_success 'gitdir required mode' ' ) ' +check_allow () { + git rev-parse --git-dir >actual && + echo .git >expect && + test_cmp expect actual +} + +check_abort () { + test_must_fail git rev-parse --git-dir +} + +# avoid git-config, since it cannot be trusted to run +# in a repository with a broken version +mkconfig () { + echo '[core]' && + echo "repositoryformatversion = $1" && + shift && + + if test $# -gt 0; then + echo '[extensions]' && + for i in "$@"; do + echo "$i" + done + fi +} + +while read outcome version extensions; do + test_expect_success "$outcome version=$version $extensions" " + mkconfig $version $extensions >.git/config && + check_${outcome} + " +done <<\EOF +allow 0 +allow 1 +allow 1 noop +abort 1 no-such-extension +allow 0 no-such-extension +EOF + test_done From 067fbd4105c5aa8260a73cc6961854be0e93fa03 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 23 Jun 2015 06:54:11 -0400 Subject: [PATCH 002/539] introduce "preciousObjects" repository extension If this extension is used in a repository, then no operations should run which may drop objects from the object storage. This can be useful if you are sharing that storage with other repositories whose refs you cannot see. For instance, if you do: $ git clone -s parent child $ git -C parent config extensions.preciousObjects true $ git -C parent config core.repositoryformatversion 1 you now have additional safety when running git in the parent repository. Prunes and repacks will bail with an error, and `git gc` will skip those operations (it will continue to pack refs and do other non-object operations). Older versions of git, when run in the repository, will fail on every operation. Note that we do not set the preciousObjects extension by default when doing a "clone -s", as doing so breaks backwards compatibility. It is a decision the user should make explicitly. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- .../technical/repository-version.txt | 7 ++++++ builtin/gc.c | 20 +++++++++-------- builtin/prune.c | 3 +++ builtin/repack.c | 3 +++ cache.h | 1 + environment.c | 1 + setup.c | 2 ++ t/t1302-repo-version.sh | 22 +++++++++++++++++++ 8 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt index 3d7106d93d6ffa..00ad37986efdce 100644 --- a/Documentation/technical/repository-version.txt +++ b/Documentation/technical/repository-version.txt @@ -79,3 +79,10 @@ The defined extensions are: This extension does not change git's behavior at all. It is useful only for testing format-1 compatibility. + +`preciousObjects` +~~~~~~~~~~~~~~~~~ + +When the config key `extensions.preciousObjects` is set to `true`, +objects in the repository MUST NOT be deleted (e.g., by `git-prune` or +`git repack -d`). diff --git a/builtin/gc.c b/builtin/gc.c index 36fe33300f644f..8b8dc6b6100814 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -352,15 +352,17 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (gc_before_repack()) return -1; - if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, repack.argv[0]); - - if (prune_expire) { - argv_array_push(&prune, prune_expire); - if (quiet) - argv_array_push(&prune, "--no-progress"); - if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, prune.argv[0]); + if (!repository_format_precious_objects) { + if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, repack.argv[0]); + + if (prune_expire) { + argv_array_push(&prune, prune_expire); + if (quiet) + argv_array_push(&prune, "--no-progress"); + if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune.argv[0]); + } } if (prune_worktrees_expire) { diff --git a/builtin/prune.c b/builtin/prune.c index 0c73246c721b33..6a58e75108c2f5 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -218,6 +218,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) return 0; } + if (repository_format_precious_objects) + die(_("cannot prune in a precious-objects repo")); + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin/repack.c b/builtin/repack.c index af7340c7bafbfb..3beda2c65ab15f 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -193,6 +193,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_repack_options, git_repack_usage, 0); + if (delete_redundant && repository_format_precious_objects) + die(_("cannot delete packs in a precious-objects repo")); + if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps; diff --git a/cache.h b/cache.h index 996584c1ceaced..b1bc4010550708 100644 --- a/cache.h +++ b/cache.h @@ -694,6 +694,7 @@ extern int grafts_replace_parents; #define GIT_REPO_VERSION 0 #define GIT_REPO_VERSION_READ 1 extern int repository_format_version; +extern int repository_format_precious_objects; extern int check_repository_format(void); #define MTIME_CHANGED 0x0001 diff --git a/environment.c b/environment.c index 61c685b8d93091..da66e829d17314 100644 --- a/environment.c +++ b/environment.c @@ -26,6 +26,7 @@ int warn_ambiguous_refs = 1; int warn_on_object_refname_ambiguity = 1; int ref_paranoia = -1; int repository_format_version; +int repository_format_precious_objects; const char *git_commit_encoding; const char *git_log_output_encoding; int shared_repository = PERM_UMASK; diff --git a/setup.c b/setup.c index 0d5384683ce2e4..8b8dca9fd21570 100644 --- a/setup.c +++ b/setup.c @@ -367,6 +367,8 @@ static int check_repo_format(const char *var, const char *value, void *cb) */ if (!strcmp(ext, "noop")) ; + else if (!strcmp(ext, "preciousobjects")) + repository_format_precious_objects = git_config_bool(var, value); else string_list_append(&unknown_extensions, ext); } diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 8dd6fd7baa6129..9bcd34969f5603 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -105,4 +105,26 @@ abort 1 no-such-extension allow 0 no-such-extension EOF +test_expect_success 'precious-objects allowed' ' + mkconfig 1 preciousObjects >.git/config && + check_allow +' + +test_expect_success 'precious-objects blocks destructive repack' ' + test_must_fail git repack -ad +' + +test_expect_success 'other repacks are OK' ' + test_commit foo && + git repack +' + +test_expect_success 'precious-objects blocks prune' ' + test_must_fail git prune +' + +test_expect_success 'gc runs without complaint' ' + git gc +' + test_done From fb70a06da2f10e8b3255abd76cd6ec1926bd9a40 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Jun 2015 14:35:13 -0700 Subject: [PATCH 003/539] rerere: fix an off-by-one non-bug When ac49f5ca (rerere "remaining", 2011-02-16) split out a new helper function check_one_conflict() out of find_conflict() function, so that the latter will use the returned value from the new helper to update the loop control variable that is an index into active_cache[], the new variable incremented the index by one too many when it found a path with only stage #1 entry at the very end of active_cache[]. This "strange" return value does not have any effect on the loop control of two callers of this function, as they all notice that active_nr+2 is larger than active_nr just like active_nr+1 is, but nevertheless it puzzles the readers when they are trying to figure out what the function is trying to do. In fact, there is no need to do an early return. The code that follows after skipping the stage #1 entry is fully prepared to handle a case where the entry is at the very end of active_cache[]. Help future readers from unnecessary confusion by dropping an early return. We skip the stage #1 entry, and if there are stage #2 and stage #3 entries for the same path, we diagnose the path as THREE_STAGED (otherwise we say PUNTED), and then we skip all entries for the same path. Signed-off-by: Junio C Hamano --- rerere.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rerere.c b/rerere.c index 31644dec04fe4a..e307711f817a9e 100644 --- a/rerere.c +++ b/rerere.c @@ -369,10 +369,8 @@ static int check_one_conflict(int i, int *type) } *type = PUNTED; - if (ce_stage(e) == 1) { - if (active_nr <= ++i) - return i + 1; - } + if (ce_stage(e) == 1) + i++; /* Only handle regular files with both stages #2 and #3 */ if (i + 1 < active_nr) { From 5eda906b2873c986fa61406dafb6acd99e70d540 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Jul 2015 15:08:03 -0700 Subject: [PATCH 004/539] rerere: handle conflicts with multiple stage #1 entries A conflicted index can have multiple stage #1 entries when dealing with a criss-cross merge and using the "resolve" merge strategy. Signed-off-by: Junio C Hamano --- rerere.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rerere.c b/rerere.c index e307711f817a9e..b453a80085a016 100644 --- a/rerere.c +++ b/rerere.c @@ -369,7 +369,7 @@ static int check_one_conflict(int i, int *type) } *type = PUNTED; - if (ce_stage(e) == 1) + while (ce_stage(active_cache[i]) == 1) i++; /* Only handle regular files with both stages #2 and #3 */ From 8d9b5a4ada8b8e187af7dbdc7bc24f6ed774df80 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 13:03:36 -0700 Subject: [PATCH 005/539] rerere: plug conflict ID leaks The merge_rr string list stores the conflict ID (a hexadecimal string that is used to index into $GIT_DIR/rr-cache) in the .util field of its elements, and when do_plain_rerere() resolves a conflict, the field is cleared. Also, when rerere_forget() recomputes the conflict ID to updates the preimage file, the conflict ID for the path is updated. We forgot to free the existing conflict ID when we did these two operations. Signed-off-by: Junio C Hamano --- rerere.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rerere.c b/rerere.c index b453a80085a016..0f40f89abd4680 100644 --- a/rerere.c +++ b/rerere.c @@ -559,6 +559,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) fprintf(stderr, "Recorded resolution for '%s'.\n", path); copy_file(rerere_path(name, "postimage"), path, 0666); mark_resolved: + free(rr->items[i].util); rr->items[i].util = NULL; } @@ -627,6 +628,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) char *hex; unsigned char sha1[20]; int ret; + struct string_list_item *item; ret = handle_cache(path, sha1, NULL); if (ret < 1) @@ -641,8 +643,9 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) handle_cache(path, sha1, rerere_path(hex, "preimage")); fprintf(stderr, "Updated preimage for '%s'\n", path); - - string_list_insert(rr, path)->util = hex; + item = string_list_insert(rr, path); + free(item->util); + item->util = hex; fprintf(stderr, "Forgot resolution for %s\n", path); return 0; } From f5800f6ad8b8cbf41a252f7ca0ae465217174c60 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Jun 2015 15:51:59 -0700 Subject: [PATCH 006/539] rerere: lift PATH_MAX limitation The MERGE_RR file records a collection of NUL-terminated entries, each of which consists of - a hash that identifies the conflict - a HT - the pathname We used to read this piece-by-piece, and worse yet, read the pathname part a byte at a time into a fixed buffer of size PATH_MAX. Instead, read a whole entry using strbuf_getwholeline() and parse out the fields. This way, we issue fewer read(2) calls and more importantly we do not have to limit the pathname to PATH_MAX. Signed-off-by: Junio C Hamano --- rerere.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/rerere.c b/rerere.c index 0f40f89abd4680..576361fb001e16 100644 --- a/rerere.c +++ b/rerere.c @@ -35,32 +35,27 @@ static int has_rerere_resolution(const char *hex) static void read_rr(struct string_list *rr) { - unsigned char sha1[20]; - char buf[PATH_MAX]; + struct strbuf buf = STRBUF_INIT; FILE *in = fopen(merge_rr_path, "r"); + if (!in) return; - while (fread(buf, 40, 1, in) == 1) { - int i; - char *name; - if (get_sha1_hex(buf, sha1)) + while (!strbuf_getwholeline(&buf, in, '\0')) { + char *path; + unsigned char sha1[20]; + + /* There has to be the hash, tab, path and then NUL */ + if (buf.len < 42 || get_sha1_hex(buf.buf, sha1)) die("corrupt MERGE_RR"); - buf[40] = '\0'; - name = xstrdup(buf); - if (fgetc(in) != '\t') + + if (buf.buf[40] != '\t') die("corrupt MERGE_RR"); - for (i = 0; i < sizeof(buf); i++) { - int c = fgetc(in); - if (c < 0) - die("corrupt MERGE_RR"); - buf[i] = c; - if (c == 0) - break; - } - if (i == sizeof(buf)) - die("filename too long"); - string_list_insert(rr, buf)->util = name; + buf.buf[40] = '\0'; + path = buf.buf + 41; + + string_list_insert(rr, path)->util = xstrdup(buf.buf); } + strbuf_release(&buf); fclose(in); } From e2cb6a950b4617edc3d07b372063b624b2b5c420 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Jun 2015 16:28:00 -0700 Subject: [PATCH 007/539] rerere: write out each record of MERGE_RR in one go Instead of writing the hash for a conflict, a HT, and the path with three separate write_in_full() calls, format them into a single record into a strbuf and write it out in one go. As a more recent "rerere remaining" codepath abuses the .util field of the merge_rr data to store a sentinel token, make sure that codepath does not call into this function (of course, "remaining" is a read-only operation and currently does not call it). Signed-off-by: Junio C Hamano --- rerere.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/rerere.c b/rerere.c index 576361fb001e16..d8518a450f3de9 100644 --- a/rerere.c +++ b/rerere.c @@ -65,16 +65,18 @@ static int write_rr(struct string_list *rr, int out_fd) { int i; for (i = 0; i < rr->nr; i++) { - const char *path; - int length; + struct strbuf buf = STRBUF_INIT; + + assert(rr->items[i].util != RERERE_RESOLVED); if (!rr->items[i].util) continue; - path = rr->items[i].string; - length = strlen(path) + 1; - if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || - write_str_in_full(out_fd, "\t") != 1 || - write_in_full(out_fd, path, length) != length) + strbuf_addf(&buf, "%s\t%s%c", + (char *)rr->items[i].util, + rr->items[i].string, 0); + if (write_in_full(out_fd, buf.buf, buf.len) != buf.len) die("unable to write rerere record"); + + strbuf_release(&buf); } if (commit_lock_file(&write_lock) != 0) die("unable to write rerere record"); From a14c7ab8f58f3b2aea99e65a74c9f9ab4f955a40 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Jun 2015 21:13:24 -0700 Subject: [PATCH 008/539] rerere: report autoupdated paths only after actually updating them Signed-off-by: Junio C Hamano --- rerere.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/rerere.c b/rerere.c index d8518a450f3de9..7fa58cc8b20d12 100644 --- a/rerere.c +++ b/rerere.c @@ -482,6 +482,8 @@ static void update_paths(struct string_list *update) struct string_list_item *item = &update->items[i]; if (add_file_to_cache(item->string, 0)) exit(128); + fprintf(stderr, "Staged '%s' using previous resolution.\n", + item->string); } if (active_cache_changed) { @@ -536,16 +538,16 @@ static int do_plain_rerere(struct string_list *rr, int fd) const char *name = (const char *)rr->items[i].util; if (has_rerere_resolution(name)) { - if (!merge(name, path)) { - const char *msg; - if (rerere_autoupdate) { - string_list_insert(&update, path); - msg = "Staged '%s' using previous resolution.\n"; - } else - msg = "Resolved '%s' using previous resolution.\n"; - fprintf(stderr, msg, path); - goto mark_resolved; - } + if (merge(name, path)) + continue; + + if (rerere_autoupdate) + string_list_insert(&update, path); + else + fprintf(stderr, + "Resolved '%s' using previous resolution.\n", + path); + goto mark_resolved; } /* Let's see if we have resolved it. */ From 67711cdc399203ec1d70cde2c3cd71e37e43da70 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 29 Jun 2015 15:05:24 -0700 Subject: [PATCH 009/539] rerere: drop want_sp parameter from is_cmarker() As the nature of the conflict marker line determines if there should be a SP and label after it, the caller shouldn't have to pass the parameter redundantly. Signed-off-by: Junio C Hamano --- rerere.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/rerere.c b/rerere.c index 7fa58cc8b20d12..2bce1c875e6c45 100644 --- a/rerere.c +++ b/rerere.c @@ -148,8 +148,25 @@ static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_) return strbuf_getwholeline(sb, io->input, '\n'); } -static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp) +/* + * Require the exact number of conflict marker letters, no more, no + * less, followed by SP or any whitespace + * (including LF). + */ +static int is_cmarker(char *buf, int marker_char, int marker_size) { + int want_sp; + + /* + * The beginning of our version and the end of their version + * always are labeled like "<<<<< ours" or ">>>>> theirs", + * hence we set want_sp for them. Note that the version from + * the common ancestor in diff3-style output is not always + * labelled (e.g. "||||| common" is often seen but "|||||" + * alone is also valid), so we do not set want_sp. + */ + want_sp = (marker_char == '<') || (marker_char == '>'); + while (marker_size--) if (*buf++ != marker_char) return 0; @@ -172,19 +189,19 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz git_SHA1_Init(&ctx); while (!io->getline(&buf, io)) { - if (is_cmarker(buf.buf, '<', marker_size, 1)) { + if (is_cmarker(buf.buf, '<', marker_size)) { if (hunk != RR_CONTEXT) goto bad; hunk = RR_SIDE_1; - } else if (is_cmarker(buf.buf, '|', marker_size, 0)) { + } else if (is_cmarker(buf.buf, '|', marker_size)) { if (hunk != RR_SIDE_1) goto bad; hunk = RR_ORIGINAL; - } else if (is_cmarker(buf.buf, '=', marker_size, 0)) { + } else if (is_cmarker(buf.buf, '=', marker_size)) { if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL) goto bad; hunk = RR_SIDE_2; - } else if (is_cmarker(buf.buf, '>', marker_size, 1)) { + } else if (is_cmarker(buf.buf, '>', marker_size)) { if (hunk != RR_SIDE_2) goto bad; if (strbuf_cmp(&one, &two) > 0) From 74444d4ec4c23d254040de7b2637660b7f141110 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 16:10:10 -0700 Subject: [PATCH 010/539] rerere: stop looping unnecessarily handle_cache() loops 3 times starting from an index entry that is unmerged, while ignoring an entry for a path that is different from what we are looking for. As the index is sorted, once we see a different path, we know we saw all stages for the path we are interested in. Just loop while we see the same path and then break, instead of continuing for 3 times. Signed-off-by: Junio C Hamano --- rerere.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/rerere.c b/rerere.c index 2bce1c875e6c45..e1d527c2506a46 100644 --- a/rerere.c +++ b/rerere.c @@ -329,24 +329,21 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu return -1; pos = -pos - 1; - for (i = 0; i < 3; i++) { + while (pos < active_nr) { enum object_type type; unsigned long size; - int j; - if (active_nr <= pos) - break; ce = active_cache[pos++]; if (ce_namelen(ce) != len || memcmp(ce->name, path, len)) - continue; - j = ce_stage(ce) - 1; - mmfile[j].ptr = read_sha1_file(ce->sha1, &type, &size); - mmfile[j].size = size; + break; + i = ce_stage(ce) - 1; + mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].size = size; } - for (i = 0; i < 3; i++) { + for (i = 0; i < 3; i++) if (!mmfile[i].ptr && !mmfile[i].size) mmfile[i].ptr = xstrdup(""); - } + /* * NEEDSWORK: handle conflicts from merges with * merge.renormalize set, too From 7d4053b69b37927c0ccd98eac41f32707227eca0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Jul 2015 15:10:52 -0700 Subject: [PATCH 011/539] rerere: do not leak mmfile[] for a path with multiple stage #1 entries A conflicted index can have multiple stage #1 entries when dealing with a criss-cross merge and using the "resolve" merge strategy. Plug the leak by reading only the first one of the same stage entries. Strictly speaking, this fix does change the semantics, in that we used to use the last stage #1 entry as the common ancestor when doing the plain-vanilla three-way merge, but with the leak fix, we will use the first stage #1 entry. But it is not a grave backward compatibility breakage. Either way, we are arbitrarily picking one of multiple stage #1 entries and using it, ignoring others, and there is no meaning in the ordering of these stage #1 entries. Signed-off-by: Junio C Hamano --- rerere.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rerere.c b/rerere.c index e1d527c2506a46..d665debf0f2a0c 100644 --- a/rerere.c +++ b/rerere.c @@ -337,8 +337,10 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu if (ce_namelen(ce) != len || memcmp(ce->name, path, len)) break; i = ce_stage(ce) - 1; - mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); - mmfile[i].size = size; + if (!mmfile[i].ptr) { + mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].size = size; + } } for (i = 0; i < 3; i++) if (!mmfile[i].ptr && !mmfile[i].size) From a96847cc1691840bd95cc56549d7c00b35f6d5a0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 22:33:19 -0700 Subject: [PATCH 012/539] rerere: explain the rerere I/O abstraction Explain the internals of rerere as in-code comments. This one covers our thin I/O abstraction to read from either a file or a memory while optionally writing out to a file. Signed-off-by: Junio C Hamano --- rerere.c | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/rerere.c b/rerere.c index d665debf0f2a0c..7db5b54838e1c2 100644 --- a/rerere.c +++ b/rerere.c @@ -83,6 +83,21 @@ static int write_rr(struct string_list *rr, int out_fd) return 0; } +/* + * "rerere" interacts with conflicted file contents using this I/O + * abstraction. It reads a conflicted contents from one place via + * "getline()" method, and optionally can write it out after + * normalizing the conflicted hunks to the "output". Subclasses of + * rerere_io embed this structure at the beginning of their own + * rerere_io object. + */ +struct rerere_io { + int (*getline)(struct strbuf *, struct rerere_io *); + FILE *output; + int wrerror; + /* some more stuff */ +}; + static void ferr_write(const void *p, size_t count, FILE *fp, int *err) { if (!count || *err) @@ -96,19 +111,15 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err) ferr_write(s, strlen(s), fp, err); } -struct rerere_io { - int (*getline)(struct strbuf *, struct rerere_io *); - FILE *output; - int wrerror; - /* some more stuff */ -}; - static void rerere_io_putstr(const char *str, struct rerere_io *io) { if (io->output) ferr_puts(str, io->output, &io->wrerror); } +/* + * Write a conflict marker to io->output (if defined). + */ static void rerere_io_putconflict(int ch, int size, struct rerere_io *io) { char buf[64]; @@ -137,11 +148,17 @@ static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io) ferr_write(mem, sz, io->output, &io->wrerror); } +/* + * Subclass of rerere_io that reads from an on-disk file + */ struct rerere_io_file { struct rerere_io io; FILE *input; }; +/* + * ... and its getline() method implementation + */ static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_) { struct rerere_io_file *io = (struct rerere_io_file *)io_; @@ -286,11 +303,18 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output return hunk_no; } +/* + * Subclass of rerere_io that reads from an in-core buffer that is a + * strbuf + */ struct rerere_io_mem { struct rerere_io io; struct strbuf input; }; +/* + * ... and its getline() method implementation + */ static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) { struct rerere_io_mem *io = (struct rerere_io_mem *)io_; From d3c2749def9563798cea3486f3793ad36d9c1030 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Jul 2015 16:01:48 -0700 Subject: [PATCH 013/539] rerere: fix benign off-by-one non-bug and clarify code rerere_io_putconflict() wants to use a limited fixed-sized buf[] on stack repeatedly to formulate a longer string, but its implementation is doubly confusing: * When it knows that the whole thing fits in buf[], it wants to fill early part of buf[] with conflict marker characters, followed by a LF and a NUL. It miscounts the size of the buffer by 1 and does not use the last byte of buf[]. * When it needs to show only the early part of a long conflict marker string (because the whole thing does not fit in buf[]), it adjusts the number of bytes shown in the current round in a strange-looking way. It makes sure that this round does not emit all bytes and leaves at least one byte to the next round, so that "it all fits" case will pick up the rest and show the terminating LF. While this is correct, one needs to stop and think for a while to realize why it is correct without an explanation. Fix the benign off-by-one, and add comments to explain the strange-looking size adjustment. Signed-off-by: Junio C Hamano --- rerere.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rerere.c b/rerere.c index 7db5b54838e1c2..6fd8c5d3afd08a 100644 --- a/rerere.c +++ b/rerere.c @@ -125,13 +125,20 @@ static void rerere_io_putconflict(int ch, int size, struct rerere_io *io) char buf[64]; while (size) { - if (size < sizeof(buf) - 2) { + if (size <= sizeof(buf) - 2) { memset(buf, ch, size); buf[size] = '\n'; buf[size + 1] = '\0'; size = 0; } else { int sz = sizeof(buf) - 1; + + /* + * Make sure we will not write everything out + * in this round by leaving at least 1 byte + * for the next round, giving the next round + * a chance to add the terminating LF. Yuck. + */ if (size <= sz) sz -= (sz - size) + 1; memset(buf, ch, sz); From 4b68c2a0877b18d1fe591dd7aee55aa1158fada0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 22:36:35 -0700 Subject: [PATCH 014/539] rerere: explain MERGE_RR management helpers Explain the internals of rerere as in-code comments, while sprinkling "NEEDSWORK" comment to highlight iffy bits and questionable assumptions. This one covers the "$GIT_DIR/MERGE_RR" file and in-core merge_rr that are used to keep track of the status of "rerere" session in progress. Signed-off-by: Junio C Hamano --- rerere.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/rerere.c b/rerere.c index 6fd8c5d3afd08a..1e68b8a62d8c9a 100644 --- a/rerere.c +++ b/rerere.c @@ -33,6 +33,13 @@ static int has_rerere_resolution(const char *hex) return !stat(rerere_path(hex, "postimage"), &st); } +/* + * $GIT_DIR/MERGE_RR file is a collection of records, each of which is + * "conflict ID", a HT and pathname, terminated with a NUL, and is + * used to keep track of the set of paths that "rerere" may need to + * work on (i.e. what is left by the previous invocation of "git + * rerere" during the current conflict resolution session). + */ static void read_rr(struct string_list *rr) { struct strbuf buf = STRBUF_INIT; @@ -403,6 +410,14 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu return hunk_no; } +/* + * Look at a cache entry at "i" and see if it is not conflicting, + * conflicting and we are willing to handle, or conflicting and + * we are unable to handle, and return the determination in *type. + * Return the cache index to be looked at next, by skipping the + * stages we have already looked at in this invocation of this + * function. + */ static int check_one_conflict(int i, int *type) { const struct cache_entry *e = active_cache[i]; @@ -434,6 +449,17 @@ static int check_one_conflict(int i, int *type) return i; } +/* + * Scan the index and find paths that have conflicts that rerere can + * handle, i.e. the ones that has both stages #2 and #3. + * + * NEEDSWORK: we do not record or replay a previous "resolve by + * deletion" for a delete-modify conflict, as that is inherently risky + * without knowing what modification is being discarded. The only + * safe case, i.e. both side doing the deletion and modification that + * are identical to the previous round, might want to be handled, + * though. + */ static int find_conflict(struct string_list *conflict) { int i; @@ -450,6 +476,21 @@ static int find_conflict(struct string_list *conflict) return 0; } +/* + * The merge_rr list is meant to hold outstanding conflicted paths + * that rerere could handle. Abuse the list by adding other types of + * entries to allow the caller to show "rerere remaining". + * + * - Conflicted paths that rerere does not handle are added + * - Conflicted paths that have been resolved are marked as such + * by storing RERERE_RESOLVED to .util field (where conflict ID + * is expected to be stored). + * + * Do *not* write MERGE_RR file out after calling this function. + * + * NEEDSWORK: we may want to fix the caller that implements "rerere + * remaining" to do this without abusing merge_rr. + */ int rerere_remaining(struct string_list *merge_rr) { int i; From cc899eca552a9b93788e6bca34aa0e4d86b251a0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 22:40:35 -0700 Subject: [PATCH 015/539] rerere: explain the primary codepath Explain the internals of rerere as in-code comments, while sprinkling "NEEDSWORK" comment to highlight iffy bits and questionable assumptions. This one covers the codepath reached from rerere(), the primary interface to the subsystem. Signed-off-by: Junio C Hamano --- rerere.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/rerere.c b/rerere.c index 1e68b8a62d8c9a..6961c1738745cb 100644 --- a/rerere.c +++ b/rerere.c @@ -206,6 +206,21 @@ static int is_cmarker(char *buf, int marker_char, int marker_size) return isspace(*buf); } +/* + * Read contents a file with conflicts, normalize the conflicts + * by (1) discarding the common ancestor version in diff3-style, + * (2) reordering our side and their side so that whichever sorts + * alphabetically earlier comes before the other one, while + * computing the "conflict ID", which is just an SHA-1 hash of + * one side of the conflict, NUL, the other side of the conflict, + * and NUL concatenated together. + * + * Return the number of conflict hunks found. + * + * NEEDSWORK: the logic and theory of operation behind this conflict + * normalization may deserve to be documented somewhere, perhaps in + * Documentation/technical/rerere.txt. + */ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size) { git_SHA_CTX ctx; @@ -276,6 +291,10 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz return hunk_no; } +/* + * Scan the path for conflicts, do the "handle_path()" thing above, and + * return the number of conflict hunks found. + */ static int handle_file(const char *path, unsigned char *sha1, const char *output) { int hunk_no = 0; @@ -515,29 +534,54 @@ int rerere_remaining(struct string_list *merge_rr) return 0; } +/* + * Find the conflict identified by "name"; the change between its + * "preimage" (i.e. a previous contents with conflict markers) and its + * "postimage" (i.e. the corresponding contents with conflicts + * resolved) may apply cleanly to the contents stored in "path", i.e. + * the conflict this time around. + * + * Returns 0 for successful replay of recorded resolution, or non-zero + * for failure. + */ static int merge(const char *name, const char *path) { int ret; mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; mmbuffer_t result = {NULL, 0}; + /* + * Normalize the conflicts in path and write it out to + * "thisimage" temporary file. + */ if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0) return 1; if (read_mmfile(&cur, rerere_path(name, "thisimage")) || - read_mmfile(&base, rerere_path(name, "preimage")) || - read_mmfile(&other, rerere_path(name, "postimage"))) { + read_mmfile(&base, rerere_path(name, "preimage")) || + read_mmfile(&other, rerere_path(name, "postimage"))) { ret = 1; goto out; } + + /* + * A three-way merge. Note that this honors user-customizable + * low-level merge driver settings. + */ ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL); if (!ret) { FILE *f; + /* + * A successful replay of recorded resolution. + * Mark that "postimage" was used to help gc. + */ if (utime(rerere_path(name, "postimage"), NULL) < 0) warning("failed utime() on %s: %s", rerere_path(name, "postimage"), strerror(errno)); + + /* Update "path" with the resolution */ f = fopen(path, "w"); if (!f) return error("Could not open %s: %s", path, @@ -590,41 +634,61 @@ static int do_plain_rerere(struct string_list *rr, int fd) find_conflict(&conflict); /* - * MERGE_RR records paths with conflicts immediately after merge - * failed. Some of the conflicted paths might have been hand resolved - * in the working tree since then, but the initial run would catch all - * and register their preimages. + * MERGE_RR records paths with conflicts immediately after + * merge failed. Some of the conflicted paths might have been + * hand resolved in the working tree since then, but the + * initial run would catch all and register their preimages. */ - for (i = 0; i < conflict.nr; i++) { const char *path = conflict.items[i].string; if (!string_list_has_string(rr, path)) { unsigned char sha1[20]; char *hex; int ret; + + /* + * Ask handle_file() to scan and assign a + * conflict ID. No need to write anything out + * yet. + */ ret = handle_file(path, sha1, NULL); if (ret < 1) continue; hex = xstrdup(sha1_to_hex(sha1)); string_list_insert(rr, path)->util = hex; + + /* + * If the directory does not exist, create + * it. mkdir_in_gitdir() will fail with + * EEXIST if there already is one. + * + * NEEDSWORK: make sure "gc" does not remove + * preimage without removing the directory. + */ if (mkdir_in_gitdir(git_path("rr-cache/%s", hex))) continue; + + /* + * We are the first to encounter this + * conflict. Ask handle_file() to write the + * normalized contents to the "preimage" file. + */ handle_file(path, NULL, rerere_path(hex, "preimage")); fprintf(stderr, "Recorded preimage for '%s'\n", path); } } /* - * Now some of the paths that had conflicts earlier might have been - * hand resolved. Others may be similar to a conflict already that - * was resolved before. + * Some of the paths that had conflicts earlier might have + * been resolved by the user. Others may be similar to a + * conflict already that was resolved before. */ - for (i = 0; i < rr->nr; i++) { int ret; const char *path = rr->items[i].string; const char *name = (const char *)rr->items[i].util; + /* Is there a recorded resolution we could attempt to apply? */ if (has_rerere_resolution(name)) { if (merge(name, path)) continue; @@ -638,13 +702,13 @@ static int do_plain_rerere(struct string_list *rr, int fd) goto mark_resolved; } - /* Let's see if we have resolved it. */ + /* Let's see if the user has resolved it. */ ret = handle_file(path, NULL, NULL); if (ret) continue; - fprintf(stderr, "Recorded resolution for '%s'.\n", path); copy_file(rerere_path(name, "postimage"), path, 0666); + fprintf(stderr, "Recorded resolution for '%s'.\n", path); mark_resolved: free(rr->items[i].util); rr->items[i].util = NULL; @@ -698,6 +762,11 @@ int setup_rerere(struct string_list *merge_rr, int flags) return fd; } +/* + * The main entry point that is called internally from codepaths that + * perform mergy operations, possibly leaving conflicted index entries + * and working tree files. + */ int rerere(int flags) { struct string_list merge_rr = STRING_LIST_INIT_DUP; From 963ec003560765bde56a880d89c75056ebe9e990 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 22:42:34 -0700 Subject: [PATCH 016/539] rerere: explain "rerere forget" codepath Explain the internals of rerere as in-code comments, while sprinkling "NEEDSWORK" comment to highlight iffy bits and questionable assumptions. This covers the codepath that implements "rerere forget". Signed-off-by: Junio C Hamano --- rerere.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rerere.c b/rerere.c index 6961c1738745cb..c3e34d10d9bacf 100644 --- a/rerere.c +++ b/rerere.c @@ -422,6 +422,10 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu strbuf_init(&io.input, 0); strbuf_attach(&io.input, result.ptr, result.size, result.size); + /* + * Grab the conflict ID and optionally write the original + * contents with conflict markers out. + */ hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size); strbuf_release(&io.input); if (io.io.output) @@ -786,9 +790,15 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) int ret; struct string_list_item *item; + /* + * Recreate the original conflict from the stages in the + * index and compute the conflict ID + */ ret = handle_cache(path, sha1, NULL); if (ret < 1) return error("Could not parse conflict hunks in '%s'", path); + + /* Nuke the recorded resolution for the conflict */ hex = xstrdup(sha1_to_hex(sha1)); filename = rerere_path(hex, "postimage"); if (unlink(filename)) @@ -796,9 +806,18 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) ? error("no remembered resolution for %s", path) : error("cannot unlink %s: %s", filename, strerror(errno))); + /* + * Update the preimage so that the user can resolve the + * conflict in the working tree, run us again to record + * the postimage. + */ handle_cache(path, sha1, rerere_path(hex, "preimage")); fprintf(stderr, "Updated preimage for '%s'\n", path); + /* + * And remember that we can record resolution for this + * conflict when the user is done. + */ item = string_list_insert(rr, path); free(item->util); item->util = hex; @@ -817,6 +836,11 @@ int rerere_forget(struct pathspec *pathspec) fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE); + /* + * The paths may have been resolved (incorrectly); + * recover the original conflicted state and then + * find the conflicted paths. + */ unmerge_cache(pathspec); find_conflict(&conflict); for (i = 0; i < conflict.nr; i++) { From e828de826bd0b852337b9625354c7c73d8de20c0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 22:43:37 -0700 Subject: [PATCH 017/539] rerere: explain the remainder Explain the internals of rerere as in-code comments, while sprinkling "NEEDSWORK" comment to highlight iffy bits and questionable assumptions. This covers the codepath that implements "rerere gc" and "rerere clear". Signed-off-by: Junio C Hamano --- rerere.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rerere.c b/rerere.c index c3e34d10d9bacf..e167670eb4773d 100644 --- a/rerere.c +++ b/rerere.c @@ -853,6 +853,9 @@ int rerere_forget(struct pathspec *pathspec) return write_rr(&merge_rr, fd); } +/* + * Garbage collection support + */ static time_t rerere_created_at(const char *name) { struct stat st; @@ -865,11 +868,19 @@ static time_t rerere_last_used_at(const char *name) return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; } +/* + * Remove the recorded resolution for a given conflict ID + */ static void unlink_rr_item(const char *name) { unlink(rerere_path(name, "thisimage")); unlink(rerere_path(name, "preimage")); unlink(rerere_path(name, "postimage")); + /* + * NEEDSWORK: what if this rmdir() fails? Wouldn't we then + * assume that we already have preimage recorded in + * do_plain_rerere()? + */ rmdir(git_path("rr-cache/%s", name)); } @@ -889,6 +900,7 @@ void rerere_gc(struct string_list *rr) dir = opendir(git_path("rr-cache")); if (!dir) die_errno("unable to open rr-cache directory"); + /* Collect stale conflict IDs ... */ while ((e = readdir(dir))) { if (is_dot_or_dotdot(e->d_name)) continue; @@ -906,11 +918,19 @@ void rerere_gc(struct string_list *rr) string_list_append(&to_remove, e->d_name); } closedir(dir); + /* ... and then remove them one-by-one */ for (i = 0; i < to_remove.nr; i++) unlink_rr_item(to_remove.items[i].string); string_list_clear(&to_remove, 0); } +/* + * During a conflict resolution, after "rerere" recorded the + * preimages, abandon them if the user did not resolve them or + * record their resolutions. And drop $GIT_DIR/MERGE_RR. + * + * NEEDSWORK: shouldn't we be calling this from "reset --hard"? + */ void rerere_clear(struct string_list *merge_rr) { int i; From 8e7768b2de8bfdf82cde565d2f42e8d7f91e74e0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jun 2015 19:36:24 -0700 Subject: [PATCH 018/539] rerere: refactor "replay" part of do_plain_rerere() Extract the body of a loop that attempts to replay recorded resolution for each conflicted path into a helper function, not because I want to call it from multiple places later, but because the logic has become too deeply nested and hard to read. Signed-off-by: Junio C Hamano --- rerere.c | 75 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/rerere.c b/rerere.c index e167670eb4773d..1d0f69d8152629 100644 --- a/rerere.c +++ b/rerere.c @@ -629,6 +629,44 @@ static void update_paths(struct string_list *update) rollback_lock_file(&index_lock); } +/* + * The path indicated by rr_item may still have conflict for which we + * have a recorded resolution, in which case replay it and optionally + * update it. Or it may have been resolved by the user and we may + * only have the preimage for that conflict, in which case the result + * needs to be recorded as a resolution in a postimage file. + */ +static void do_rerere_one_path(struct string_list_item *rr_item, + struct string_list *update) +{ + const char *path = rr_item->string; + const char *name = (const char *)rr_item->util; + + /* Is there a recorded resolution we could attempt to apply? */ + if (has_rerere_resolution(name)) { + if (merge(name, path)) + return; /* failed to replay */ + + if (rerere_autoupdate) + string_list_insert(update, path); + else + fprintf(stderr, + "Resolved '%s' using previous resolution.\n", + path); + goto mark_resolved; + } + + /* Let's see if the user has resolved it. */ + if (handle_file(path, NULL, NULL)) + return; /* not yet resolved */ + + copy_file(rerere_path(name, "postimage"), path, 0666); + fprintf(stderr, "Recorded resolution for '%s'.\n", path); +mark_resolved: + free(rr_item->util); + rr_item->util = NULL; +} + static int do_plain_rerere(struct string_list *rr, int fd) { struct string_list conflict = STRING_LIST_INIT_DUP; @@ -682,41 +720,8 @@ static int do_plain_rerere(struct string_list *rr, int fd) } } - /* - * Some of the paths that had conflicts earlier might have - * been resolved by the user. Others may be similar to a - * conflict already that was resolved before. - */ - for (i = 0; i < rr->nr; i++) { - int ret; - const char *path = rr->items[i].string; - const char *name = (const char *)rr->items[i].util; - - /* Is there a recorded resolution we could attempt to apply? */ - if (has_rerere_resolution(name)) { - if (merge(name, path)) - continue; - - if (rerere_autoupdate) - string_list_insert(&update, path); - else - fprintf(stderr, - "Resolved '%s' using previous resolution.\n", - path); - goto mark_resolved; - } - - /* Let's see if the user has resolved it. */ - ret = handle_file(path, NULL, NULL); - if (ret) - continue; - - copy_file(rerere_path(name, "postimage"), path, 0666); - fprintf(stderr, "Recorded resolution for '%s'.\n", path); - mark_resolved: - free(rr->items[i].util); - rr->items[i].util = NULL; - } + for (i = 0; i < rr->nr; i++) + do_rerere_one_path(&rr->items[i], &update); if (update.nr) update_paths(&update); From c7a25d3790bdbc486362084238db5a773f728570 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Jul 2015 17:17:38 -0700 Subject: [PATCH 019/539] rerere: further de-dent do_plain_rerere() It's just easier to follow this way. Signed-off-by: Junio C Hamano --- rerere.c | 65 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/rerere.c b/rerere.c index 1d0f69d8152629..7b4028cf11e46b 100644 --- a/rerere.c +++ b/rerere.c @@ -682,42 +682,43 @@ static int do_plain_rerere(struct string_list *rr, int fd) * initial run would catch all and register their preimages. */ for (i = 0; i < conflict.nr; i++) { + unsigned char sha1[20]; + char *hex; + int ret; const char *path = conflict.items[i].string; - if (!string_list_has_string(rr, path)) { - unsigned char sha1[20]; - char *hex; - int ret; - /* - * Ask handle_file() to scan and assign a - * conflict ID. No need to write anything out - * yet. - */ - ret = handle_file(path, sha1, NULL); - if (ret < 1) - continue; - hex = xstrdup(sha1_to_hex(sha1)); - string_list_insert(rr, path)->util = hex; + if (string_list_has_string(rr, path)) + continue; - /* - * If the directory does not exist, create - * it. mkdir_in_gitdir() will fail with - * EEXIST if there already is one. - * - * NEEDSWORK: make sure "gc" does not remove - * preimage without removing the directory. - */ - if (mkdir_in_gitdir(git_path("rr-cache/%s", hex))) - continue; + /* + * Ask handle_file() to scan and assign a + * conflict ID. No need to write anything out + * yet. + */ + ret = handle_file(path, sha1, NULL); + if (ret < 1) + continue; + hex = xstrdup(sha1_to_hex(sha1)); + string_list_insert(rr, path)->util = hex; - /* - * We are the first to encounter this - * conflict. Ask handle_file() to write the - * normalized contents to the "preimage" file. - */ - handle_file(path, NULL, rerere_path(hex, "preimage")); - fprintf(stderr, "Recorded preimage for '%s'\n", path); - } + /* + * If the directory does not exist, create + * it. mkdir_in_gitdir() will fail with + * EEXIST if there already is one. + * + * NEEDSWORK: make sure "gc" does not remove + * preimage without removing the directory. + */ + if (mkdir_in_gitdir(git_path("rr-cache/%s", hex))) + continue; + + /* + * We are the first to encounter this + * conflict. Ask handle_file() to write the + * normalized contents to the "preimage" file. + */ + handle_file(path, NULL, rerere_path(hex, "preimage")); + fprintf(stderr, "Recorded preimage for '%s'\n", path); } for (i = 0; i < rr->nr; i++) From 925d73c4217388838e36bfed85553132c458c7d0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Jul 2015 14:18:09 -0700 Subject: [PATCH 020/539] rerere: further clarify do_rerere_one_path() Signed-off-by: Junio C Hamano --- rerere.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/rerere.c b/rerere.c index 7b4028cf11e46b..80be303bd91869 100644 --- a/rerere.c +++ b/rerere.c @@ -653,16 +653,13 @@ static void do_rerere_one_path(struct string_list_item *rr_item, fprintf(stderr, "Resolved '%s' using previous resolution.\n", path); - goto mark_resolved; + } else if (!handle_file(path, NULL, NULL)) { + /* The user has resolved it. */ + copy_file(rerere_path(name, "postimage"), path, 0666); + fprintf(stderr, "Recorded resolution for '%s'.\n", path); + } else { + return; } - - /* Let's see if the user has resolved it. */ - if (handle_file(path, NULL, NULL)) - return; /* not yet resolved */ - - copy_file(rerere_path(name, "postimage"), path, 0666); - fprintf(stderr, "Recorded resolution for '%s'.\n", path); -mark_resolved: free(rr_item->util); rr_item->util = NULL; } From 18bb99342fdb4b612ae45be3fef084ceebd498a0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Jul 2015 14:45:55 -0700 Subject: [PATCH 021/539] rerere: call conflict-ids IDs Most places we call conflict IDs "name" and some others we call them "hex"; update all of them to "id". Signed-off-by: Junio C Hamano --- builtin/rerere.c | 4 +-- rerere.c | 76 ++++++++++++++++++++++++------------------------ rerere.h | 2 +- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/builtin/rerere.c b/builtin/rerere.c index 98eb8c5404914e..81730bb5e677cb 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -103,8 +103,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) } else if (!strcmp(argv[0], "diff")) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; - const char *name = (const char *)merge_rr.items[i].util; - diff_two(rerere_path(name, "preimage"), path, path, path); + const char *id = (const char *)merge_rr.items[i].util; + diff_two(rerere_path(id, "preimage"), path, path, path); } else usage_with_options(rerere_usage, options); diff --git a/rerere.c b/rerere.c index 80be303bd91869..2865aeaf704e59 100644 --- a/rerere.c +++ b/rerere.c @@ -22,15 +22,15 @@ static int rerere_autoupdate; static char *merge_rr_path; -const char *rerere_path(const char *hex, const char *file) +const char *rerere_path(const char *id, const char *file) { - return git_path("rr-cache/%s/%s", hex, file); + return git_path("rr-cache/%s/%s", id, file); } -static int has_rerere_resolution(const char *hex) +static int has_rerere_resolution(const char *id) { struct stat st; - return !stat(rerere_path(hex, "postimage"), &st); + return !stat(rerere_path(id, "postimage"), &st); } /* @@ -539,7 +539,7 @@ int rerere_remaining(struct string_list *merge_rr) } /* - * Find the conflict identified by "name"; the change between its + * Find the conflict identified by "id"; the change between its * "preimage" (i.e. a previous contents with conflict markers) and its * "postimage" (i.e. the corresponding contents with conflicts * resolved) may apply cleanly to the contents stored in "path", i.e. @@ -548,7 +548,7 @@ int rerere_remaining(struct string_list *merge_rr) * Returns 0 for successful replay of recorded resolution, or non-zero * for failure. */ -static int merge(const char *name, const char *path) +static int merge(const char *id, const char *path) { int ret; mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; @@ -558,12 +558,12 @@ static int merge(const char *name, const char *path) * Normalize the conflicts in path and write it out to * "thisimage" temporary file. */ - if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0) + if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) return 1; - if (read_mmfile(&cur, rerere_path(name, "thisimage")) || - read_mmfile(&base, rerere_path(name, "preimage")) || - read_mmfile(&other, rerere_path(name, "postimage"))) { + if (read_mmfile(&cur, rerere_path(id, "thisimage")) || + read_mmfile(&base, rerere_path(id, "preimage")) || + read_mmfile(&other, rerere_path(id, "postimage"))) { ret = 1; goto out; } @@ -580,9 +580,9 @@ static int merge(const char *name, const char *path) * A successful replay of recorded resolution. * Mark that "postimage" was used to help gc. */ - if (utime(rerere_path(name, "postimage"), NULL) < 0) + if (utime(rerere_path(id, "postimage"), NULL) < 0) warning("failed utime() on %s: %s", - rerere_path(name, "postimage"), + rerere_path(id, "postimage"), strerror(errno)); /* Update "path" with the resolution */ @@ -640,11 +640,11 @@ static void do_rerere_one_path(struct string_list_item *rr_item, struct string_list *update) { const char *path = rr_item->string; - const char *name = (const char *)rr_item->util; + const char *id = (const char *)rr_item->util; /* Is there a recorded resolution we could attempt to apply? */ - if (has_rerere_resolution(name)) { - if (merge(name, path)) + if (has_rerere_resolution(id)) { + if (merge(id, path)) return; /* failed to replay */ if (rerere_autoupdate) @@ -655,7 +655,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item, path); } else if (!handle_file(path, NULL, NULL)) { /* The user has resolved it. */ - copy_file(rerere_path(name, "postimage"), path, 0666); + copy_file(rerere_path(id, "postimage"), path, 0666); fprintf(stderr, "Recorded resolution for '%s'.\n", path); } else { return; @@ -680,7 +680,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) */ for (i = 0; i < conflict.nr; i++) { unsigned char sha1[20]; - char *hex; + char *id; int ret; const char *path = conflict.items[i].string; @@ -695,8 +695,8 @@ static int do_plain_rerere(struct string_list *rr, int fd) ret = handle_file(path, sha1, NULL); if (ret < 1) continue; - hex = xstrdup(sha1_to_hex(sha1)); - string_list_insert(rr, path)->util = hex; + id = xstrdup(sha1_to_hex(sha1)); + string_list_insert(rr, path)->util = id; /* * If the directory does not exist, create @@ -706,7 +706,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) * NEEDSWORK: make sure "gc" does not remove * preimage without removing the directory. */ - if (mkdir_in_gitdir(git_path("rr-cache/%s", hex))) + if (mkdir_in_gitdir(git_path("rr-cache/%s", id))) continue; /* @@ -714,7 +714,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) * conflict. Ask handle_file() to write the * normalized contents to the "preimage" file. */ - handle_file(path, NULL, rerere_path(hex, "preimage")); + handle_file(path, NULL, rerere_path(id, "preimage")); fprintf(stderr, "Recorded preimage for '%s'\n", path); } @@ -788,7 +788,7 @@ int rerere(int flags) static int rerere_forget_one_path(const char *path, struct string_list *rr) { const char *filename; - char *hex; + char *id; unsigned char sha1[20]; int ret; struct string_list_item *item; @@ -802,8 +802,8 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) return error("Could not parse conflict hunks in '%s'", path); /* Nuke the recorded resolution for the conflict */ - hex = xstrdup(sha1_to_hex(sha1)); - filename = rerere_path(hex, "postimage"); + id = xstrdup(sha1_to_hex(sha1)); + filename = rerere_path(id, "postimage"); if (unlink(filename)) return (errno == ENOENT ? error("no remembered resolution for %s", path) @@ -814,7 +814,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) * conflict in the working tree, run us again to record * the postimage. */ - handle_cache(path, sha1, rerere_path(hex, "preimage")); + handle_cache(path, sha1, rerere_path(id, "preimage")); fprintf(stderr, "Updated preimage for '%s'\n", path); /* @@ -823,7 +823,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) */ item = string_list_insert(rr, path); free(item->util); - item->util = hex; + item->util = id; fprintf(stderr, "Forgot resolution for %s\n", path); return 0; } @@ -859,32 +859,32 @@ int rerere_forget(struct pathspec *pathspec) /* * Garbage collection support */ -static time_t rerere_created_at(const char *name) +static time_t rerere_created_at(const char *id) { struct stat st; - return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; + return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime; } -static time_t rerere_last_used_at(const char *name) +static time_t rerere_last_used_at(const char *id) { struct stat st; - return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; + return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime; } /* * Remove the recorded resolution for a given conflict ID */ -static void unlink_rr_item(const char *name) +static void unlink_rr_item(const char *id) { - unlink(rerere_path(name, "thisimage")); - unlink(rerere_path(name, "preimage")); - unlink(rerere_path(name, "postimage")); + unlink(rerere_path(id, "thisimage")); + unlink(rerere_path(id, "preimage")); + unlink(rerere_path(id, "postimage")); /* * NEEDSWORK: what if this rmdir() fails? Wouldn't we then * assume that we already have preimage recorded in * do_plain_rerere()? */ - rmdir(git_path("rr-cache/%s", name)); + rmdir(git_path("rr-cache/%s", id)); } void rerere_gc(struct string_list *rr) @@ -939,9 +939,9 @@ void rerere_clear(struct string_list *merge_rr) int i; for (i = 0; i < merge_rr->nr; i++) { - const char *name = (const char *)merge_rr->items[i].util; - if (!has_rerere_resolution(name)) - unlink_rr_item(name); + const char *id = (const char *)merge_rr->items[i].util; + if (!has_rerere_resolution(id)) + unlink_rr_item(id); } unlink_or_warn(git_path("MERGE_RR")); } diff --git a/rerere.h b/rerere.h index 2956c2edf2f0f8..f998ebae6e18ca 100644 --- a/rerere.h +++ b/rerere.h @@ -17,7 +17,7 @@ extern void *RERERE_RESOLVED; extern int setup_rerere(struct string_list *, int); extern int rerere(int); -extern const char *rerere_path(const char *hex, const char *file); +extern const char *rerere_path(const char *id, const char *file); extern int rerere_forget(struct pathspec *); extern int rerere_remaining(struct string_list *); extern void rerere_clear(struct string_list *); From 1d51eced103f1c3e36beb6c1e01a413660910a50 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Jul 2015 17:38:34 -0700 Subject: [PATCH 022/539] rerere: use "struct rerere_id" instead of "char *" for conflict ID This gives a thin abstraction between the conflict ID that is a hash value obtained by inspecting the conflicts and the name of the directory under $GIT_DIR/rr-cache/, in which the previous resolution is recorded to be replayed. The plan is to make sure that the presence of the directory does not imply the presense of a previous resolution and vice-versa, and later allow us to have more than one pair of for a given conflict ID. Signed-off-by: Junio C Hamano --- builtin/rerere.c | 2 +- rerere.c | 99 +++++++++++++++++++++++++++++++++++------------- rerere.h | 12 +++++- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/builtin/rerere.c b/builtin/rerere.c index 81730bb5e677cb..fd229a7c7dae54 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -103,7 +103,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) } else if (!strcmp(argv[0], "diff")) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; - const char *id = (const char *)merge_rr.items[i].util; + const struct rerere_id *id = merge_rr.items[i].util; diff_two(rerere_path(id, "preimage"), path, path, path); } else diff --git a/rerere.c b/rerere.c index 2865aeaf704e59..93fdeec809c44c 100644 --- a/rerere.c +++ b/rerere.c @@ -22,17 +22,43 @@ static int rerere_autoupdate; static char *merge_rr_path; -const char *rerere_path(const char *id, const char *file) +static void free_rerere_id(struct string_list_item *item) { - return git_path("rr-cache/%s/%s", id, file); + free(item->util); +} + +static const char *rerere_id_hex(const struct rerere_id *id) +{ + return id->hex; +} + +const char *rerere_path(const struct rerere_id *id, const char *file) +{ + if (!file) + return git_path("rr-cache/%s", rerere_id_hex(id)); + + return git_path("rr-cache/%s/%s", rerere_id_hex(id), file); } -static int has_rerere_resolution(const char *id) +static int has_rerere_resolution(const struct rerere_id *id) { struct stat st; + return !stat(rerere_path(id, "postimage"), &st); } +static struct rerere_id *new_rerere_id_hex(char *hex) +{ + struct rerere_id *id = xmalloc(sizeof(*id)); + strcpy(id->hex, hex); + return id; +} + +static struct rerere_id *new_rerere_id(unsigned char *sha1) +{ + return new_rerere_id_hex(sha1_to_hex(sha1)); +} + /* * $GIT_DIR/MERGE_RR file is a collection of records, each of which is * "conflict ID", a HT and pathname, terminated with a NUL, and is @@ -50,6 +76,7 @@ static void read_rr(struct string_list *rr) while (!strbuf_getwholeline(&buf, in, '\0')) { char *path; unsigned char sha1[20]; + struct rerere_id *id; /* There has to be the hash, tab, path and then NUL */ if (buf.len < 42 || get_sha1_hex(buf.buf, sha1)) @@ -59,8 +86,8 @@ static void read_rr(struct string_list *rr) die("corrupt MERGE_RR"); buf.buf[40] = '\0'; path = buf.buf + 41; - - string_list_insert(rr, path)->util = xstrdup(buf.buf); + id = new_rerere_id_hex(buf.buf); + string_list_insert(rr, path)->util = id; } strbuf_release(&buf); fclose(in); @@ -73,12 +100,15 @@ static int write_rr(struct string_list *rr, int out_fd) int i; for (i = 0; i < rr->nr; i++) { struct strbuf buf = STRBUF_INIT; + struct rerere_id *id; assert(rr->items[i].util != RERERE_RESOLVED); - if (!rr->items[i].util) + + id = rr->items[i].util; + if (!id) continue; strbuf_addf(&buf, "%s\t%s%c", - (char *)rr->items[i].util, + rerere_id_hex(id), rr->items[i].string, 0); if (write_in_full(out_fd, buf.buf, buf.len) != buf.len) die("unable to write rerere record"); @@ -530,7 +560,7 @@ int rerere_remaining(struct string_list *merge_rr) struct string_list_item *it; it = string_list_lookup(merge_rr, (const char *)e->name); if (it != NULL) { - free(it->util); + free_rerere_id(it); it->util = RERERE_RESOLVED; } } @@ -548,7 +578,7 @@ int rerere_remaining(struct string_list *merge_rr) * Returns 0 for successful replay of recorded resolution, or non-zero * for failure. */ -static int merge(const char *id, const char *path) +static int merge(const struct rerere_id *id, const char *path) { int ret; mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; @@ -582,8 +612,8 @@ static int merge(const char *id, const char *path) */ if (utime(rerere_path(id, "postimage"), NULL) < 0) warning("failed utime() on %s: %s", - rerere_path(id, "postimage"), - strerror(errno)); + rerere_path(id, "postimage"), + strerror(errno)); /* Update "path" with the resolution */ f = fopen(path, "w"); @@ -640,7 +670,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item, struct string_list *update) { const char *path = rr_item->string; - const char *id = (const char *)rr_item->util; + const struct rerere_id *id = rr_item->util; /* Is there a recorded resolution we could attempt to apply? */ if (has_rerere_resolution(id)) { @@ -660,7 +690,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item, } else { return; } - free(rr_item->util); + free_rerere_id(rr_item); rr_item->util = NULL; } @@ -679,10 +709,10 @@ static int do_plain_rerere(struct string_list *rr, int fd) * initial run would catch all and register their preimages. */ for (i = 0; i < conflict.nr; i++) { + struct rerere_id *id; unsigned char sha1[20]; - char *id; - int ret; const char *path = conflict.items[i].string; + int ret; if (string_list_has_string(rr, path)) continue; @@ -695,7 +725,8 @@ static int do_plain_rerere(struct string_list *rr, int fd) ret = handle_file(path, sha1, NULL); if (ret < 1) continue; - id = xstrdup(sha1_to_hex(sha1)); + + id = new_rerere_id(sha1); string_list_insert(rr, path)->util = id; /* @@ -706,7 +737,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) * NEEDSWORK: make sure "gc" does not remove * preimage without removing the directory. */ - if (mkdir_in_gitdir(git_path("rr-cache/%s", id))) + if (mkdir_in_gitdir(rerere_path(id, NULL))) continue; /* @@ -788,7 +819,7 @@ int rerere(int flags) static int rerere_forget_one_path(const char *path, struct string_list *rr) { const char *filename; - char *id; + struct rerere_id *id; unsigned char sha1[20]; int ret; struct string_list_item *item; @@ -802,7 +833,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) return error("Could not parse conflict hunks in '%s'", path); /* Nuke the recorded resolution for the conflict */ - id = xstrdup(sha1_to_hex(sha1)); + id = new_rerere_id(sha1); filename = rerere_path(id, "postimage"); if (unlink(filename)) return (errno == ENOENT @@ -822,7 +853,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) * conflict when the user is done. */ item = string_list_insert(rr, path); - free(item->util); + free_rerere_id(item); item->util = id; fprintf(stderr, "Forgot resolution for %s\n", path); return 0; @@ -859,22 +890,38 @@ int rerere_forget(struct pathspec *pathspec) /* * Garbage collection support */ -static time_t rerere_created_at(const char *id) + +/* + * Note that this is not reentrant but is used only one-at-a-time + * so it does not matter right now. + */ +static struct rerere_id *dirname_to_id(const char *name) +{ + static struct rerere_id id; + strcpy(id.hex, name); + return &id; +} + +static time_t rerere_created_at(const char *dir_name) { struct stat st; + struct rerere_id *id = dirname_to_id(dir_name); + return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime; } -static time_t rerere_last_used_at(const char *id) +static time_t rerere_last_used_at(const char *dir_name) { struct stat st; + struct rerere_id *id = dirname_to_id(dir_name); + return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime; } /* * Remove the recorded resolution for a given conflict ID */ -static void unlink_rr_item(const char *id) +static void unlink_rr_item(struct rerere_id *id) { unlink(rerere_path(id, "thisimage")); unlink(rerere_path(id, "preimage")); @@ -884,7 +931,7 @@ static void unlink_rr_item(const char *id) * assume that we already have preimage recorded in * do_plain_rerere()? */ - rmdir(git_path("rr-cache/%s", id)); + rmdir(rerere_path(id, NULL)); } void rerere_gc(struct string_list *rr) @@ -923,7 +970,7 @@ void rerere_gc(struct string_list *rr) closedir(dir); /* ... and then remove them one-by-one */ for (i = 0; i < to_remove.nr; i++) - unlink_rr_item(to_remove.items[i].string); + unlink_rr_item(dirname_to_id(to_remove.items[i].string)); string_list_clear(&to_remove, 0); } @@ -939,7 +986,7 @@ void rerere_clear(struct string_list *merge_rr) int i; for (i = 0; i < merge_rr->nr; i++) { - const char *id = (const char *)merge_rr->items[i].util; + struct rerere_id *id = merge_rr->items[i].util; if (!has_rerere_resolution(id)) unlink_rr_item(id); } diff --git a/rerere.h b/rerere.h index f998ebae6e18ca..ce545d0c045b3d 100644 --- a/rerere.h +++ b/rerere.h @@ -15,9 +15,19 @@ struct pathspec; */ extern void *RERERE_RESOLVED; +struct rerere_id { + char hex[41]; +}; + extern int setup_rerere(struct string_list *, int); extern int rerere(int); -extern const char *rerere_path(const char *id, const char *file); +/* + * Given the conflict ID and the name of a "file" used for replaying + * the recorded resolution (e.g. "preimage", "postimage"), return the + * path to that filesystem entity. With "file" specified with NULL, + * return the path to the directory that houses these files. + */ +extern const char *rerere_path(const struct rerere_id *, const char *file); extern int rerere_forget(struct pathspec *); extern int rerere_remaining(struct string_list *); extern void rerere_clear(struct string_list *); From 15ed07d532db743a2a397a38bacc1f20e54b2c80 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Jul 2015 15:32:53 -0700 Subject: [PATCH 023/539] rerere: un-nest merge() further By consistently using "upon failure, set 'ret' and jump to out" pattern, flatten the function further. Signed-off-by: Junio C Hamano --- rerere.c | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/rerere.c b/rerere.c index 93fdeec809c44c..9bef24f5b29e43 100644 --- a/rerere.c +++ b/rerere.c @@ -580,6 +580,7 @@ int rerere_remaining(struct string_list *merge_rr) */ static int merge(const struct rerere_id *id, const char *path) { + FILE *f; int ret; mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; mmbuffer_t result = {NULL, 0}; @@ -588,8 +589,10 @@ static int merge(const struct rerere_id *id, const char *path) * Normalize the conflicts in path and write it out to * "thisimage" temporary file. */ - if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) - return 1; + if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) { + ret = 1; + goto out; + } if (read_mmfile(&cur, rerere_path(id, "thisimage")) || read_mmfile(&base, rerere_path(id, "preimage")) || @@ -603,29 +606,28 @@ static int merge(const struct rerere_id *id, const char *path) * low-level merge driver settings. */ ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL); - if (!ret) { - FILE *f; + if (ret) + goto out; - /* - * A successful replay of recorded resolution. - * Mark that "postimage" was used to help gc. - */ - if (utime(rerere_path(id, "postimage"), NULL) < 0) - warning("failed utime() on %s: %s", - rerere_path(id, "postimage"), - strerror(errno)); - - /* Update "path" with the resolution */ - f = fopen(path, "w"); - if (!f) - return error("Could not open %s: %s", path, - strerror(errno)); - if (fwrite(result.ptr, result.size, 1, f) != 1) - error("Could not write %s: %s", path, strerror(errno)); - if (fclose(f)) - return error("Writing %s failed: %s", path, - strerror(errno)); - } + /* + * A successful replay of recorded resolution. + * Mark that "postimage" was used to help gc. + */ + if (utime(rerere_path(id, "postimage"), NULL) < 0) + warning("failed utime() on %s: %s", + rerere_path(id, "postimage"), + strerror(errno)); + + /* Update "path" with the resolution */ + f = fopen(path, "w"); + if (!f) + return error("Could not open %s: %s", path, + strerror(errno)); + if (fwrite(result.ptr, result.size, 1, f) != 1) + error("Could not write %s: %s", path, strerror(errno)); + if (fclose(f)) + return error("Writing %s failed: %s", path, + strerror(errno)); out: free(cur.ptr); From af83bafa4867ba16368e58f36e8311e9591e68f4 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 9 Jul 2015 11:33:21 +0530 Subject: [PATCH 024/539] t6302: for-each-ref tests for ref-filter APIs Add a test suite for testing the ref-filter APIs used by for-each-ref. We just intialize the test suite for now. More tests will be added in the following patches as more options are added to for-each-ref. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- t/t6302-for-each-ref-filter.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 t/t6302-for-each-ref-filter.sh diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh new file mode 100755 index 00000000000000..ae751163f79e3a --- /dev/null +++ b/t/t6302-for-each-ref-filter.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='test for-each-refs usage of ref-filter APIs' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh + +if ! test_have_prereq GPG +then + skip_all="skipping for-each-ref tests, GPG not available" + test_done +fi + +test_expect_success 'setup some history and refs' ' + test_commit one && + test_commit two && + test_commit three && + git checkout -b side && + test_commit four && + git tag -s -m "A signed tag message" signed-tag && + git tag -s -m "Annonated doubly" double-tag signed-tag && + git checkout master && + git update-ref refs/odd/spot master +' + +test_done From b2172fdf702426cb4eba093b5504d0a0e9e59746 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:08 +0530 Subject: [PATCH 025/539] tag: libify parse_opt_points_at() Rename 'parse_opt_points_at()' to 'parse_opt_object_name()' and move it from 'tag.c' to 'parse-options'. This now acts as a common parse_opt function which accepts an objectname and stores it into a sha1_array. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/tag.c | 21 ++------------------- parse-options-cb.c | 17 +++++++++++++++++ parse-options.h | 1 + 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/builtin/tag.c b/builtin/tag.c index 5f6cdc5a03cd0d..e36c43ec4b03f7 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -546,23 +546,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) return check_refname_format(sb->buf, 0); } -static int parse_opt_points_at(const struct option *opt __attribute__((unused)), - const char *arg, int unset) -{ - unsigned char sha1[20]; - - if (unset) { - sha1_array_clear(&points_at); - return 0; - } - if (!arg) - return error(_("switch 'points-at' requires an object")); - if (get_sha1(arg, sha1)) - return error(_("malformed object name '%s'"), arg); - sha1_array_append(&points_at, sha1); - return 0; -} - static int parse_opt_sort(const struct option *opt, const char *arg, int unset) { int *sort = opt->value; @@ -625,8 +608,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) parse_opt_with_commit, (intptr_t)"HEAD", }, { - OPTION_CALLBACK, 0, "points-at", NULL, N_("object"), - N_("print only tags of the object"), 0, parse_opt_points_at + OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"), + N_("print only tags of the object"), 0, parse_opt_object_name }, OPT_END() }; diff --git a/parse-options-cb.c b/parse-options-cb.c index be8c413cfebb41..de75411086c861 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -4,6 +4,7 @@ #include "commit.h" #include "color.h" #include "string-list.h" +#include "sha1-array.h" /*----- some often used options -----*/ @@ -92,6 +93,22 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) return 0; } +int parse_opt_object_name(const struct option *opt, const char *arg, int unset) +{ + unsigned char sha1[20]; + + if (unset) { + sha1_array_clear(opt->value); + return 0; + } + if (!arg) + return -1; + if (get_sha1(arg, sha1)) + return error(_("malformed object name '%s'"), arg); + sha1_array_append(opt->value, sha1); + return 0; +} + int parse_opt_tertiary(const struct option *opt, const char *arg, int unset) { int *target = opt->value; diff --git a/parse-options.h b/parse-options.h index c71e9da4f820ab..36c71fedf14ed0 100644 --- a/parse-options.h +++ b/parse-options.h @@ -220,6 +220,7 @@ extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_expiry_date_cb(const struct option *, const char *, int); extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); +extern int parse_opt_object_name(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); extern int parse_opt_string_list(const struct option *, const char *, int); From 68411046b5067de9c378d1f58313f2fae288286c Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:09 +0530 Subject: [PATCH 026/539] ref-filter: implement '--points-at' option In 'tag -l' we have '--points-at' option which lets users list only tags of a given object. Implement this option in 'ref-filter.{c,h}' so that other commands can benefit from this. This is duplicated from tag.c, we will eventually remove that when we port tag.c to use ref-filter APIs. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/tag.c | 4 ++++ ref-filter.c | 35 +++++++++++++++++++++++++++++++++++ ref-filter.h | 1 + 3 files changed, 40 insertions(+) diff --git a/builtin/tag.c b/builtin/tag.c index e36c43ec4b03f7..280981f573be99 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -56,6 +56,10 @@ static int match_pattern(const char **patterns, const char *ref) return 0; } +/* + * This is currently duplicated in ref-filter.c, and will eventually be + * removed as we port tag.c to use the ref-filter APIs. + */ static const unsigned char *match_points_at(const char *refname, const unsigned char *sha1) { diff --git a/ref-filter.c b/ref-filter.c index 3fbbbeb88fedda..2efe30a67e6d4e 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -842,6 +842,38 @@ static int match_name_as_path(const char **pattern, const char *refname) return 0; } +/* + * Given a ref (sha1, refname), check if the ref belongs to the array + * of sha1s. If the given ref is a tag, check if the given tag points + * at one of the sha1s in the given sha1 array. + * the given sha1_array. + * NEEDSWORK: + * 1. Only a single level of inderection is obtained, we might want to + * change this to account for multiple levels (e.g. annotated tags + * pointing to annotated tags pointing to a commit.) + * 2. As the refs are cached we might know what refname peels to without + * the need to parse the object via parse_object(). peel_ref() might be a + * more efficient alternative to obtain the pointee. + */ +static const unsigned char *match_points_at(struct sha1_array *points_at, + const unsigned char *sha1, + const char *refname) +{ + const unsigned char *tagged_sha1 = NULL; + struct object *obj; + + if (sha1_array_lookup(points_at, sha1) >= 0) + return sha1; + obj = parse_object(sha1); + if (!obj) + die(_("malformed object at '%s'"), refname); + if (obj->type == OBJ_TAG) + tagged_sha1 = ((struct tag *)obj)->tagged->sha1; + if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0) + return tagged_sha1; + return NULL; +} + /* Allocate space for a new ref_array_item and copy the objectname and flag to it */ static struct ref_array_item *new_ref_array_item(const char *refname, const unsigned char *objectname, @@ -875,6 +907,9 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname)) return 0; + if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname)) + return 0; + /* * We do not open the object yet; sort may only need refname * to do its job and the resulting list may yet to be pruned diff --git a/ref-filter.h b/ref-filter.h index 699798400b329a..c2856b829570a5 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -42,6 +42,7 @@ struct ref_array { struct ref_filter { const char **name_patterns; + struct sha1_array points_at; }; struct ref_filter_cbdata { From d325406ef2f3c819d02ac838fb2a3f8e021d08ae Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:10 +0530 Subject: [PATCH 027/539] for-each-ref: add '--points-at' option Add the '--points-at' option provided by 'ref-filter'. The option lets the user to list only refs which points at the given object. Add documentation and tests for the same. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 3 +++ builtin/for-each-ref.c | 9 +++++++-- t/t6302-for-each-ref-filter.sh | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 7f8d9a5b5f358b..ff0283beca0c9f 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git for-each-ref' [--count=] [--shell|--perl|--python|--tcl] [(--sort=)...] [--format=] [...] + [--points-at ] DESCRIPTION ----------- @@ -62,6 +63,8 @@ OPTIONS the specified host language. This is meant to produce a scriptlet that can directly be `eval`ed. +--points-at :: + Only list refs which points at the given object. FIELD NAMES ----------- diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 7919206187c996..ae5419e0de74f9 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -7,6 +7,7 @@ static char const * const for_each_ref_usage[] = { N_("git for-each-ref [] []"), + N_("git for-each-ref [--points-at ]"), NULL }; @@ -34,9 +35,15 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), N_("field name to sort on"), &parse_opt_ref_sorting), + OPT_CALLBACK(0, "points-at", &filter.points_at, + N_("object"), N_("print only refs which points at the given object"), + parse_opt_object_name), OPT_END(), }; + memset(&array, 0, sizeof(array)); + memset(&filter, 0, sizeof(filter)); + parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); if (maxcount < 0) { error("invalid --count argument: `%d'", maxcount); @@ -55,8 +62,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) /* for warn_ambiguous_refs */ git_config(git_default_config, NULL); - memset(&array, 0, sizeof(array)); - memset(&filter, 0, sizeof(filter)); filter.name_patterns = argv; filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); ref_array_sort(sorting, &array); diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index ae751163f79e3a..98ff4d5e43b6f6 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -23,4 +23,24 @@ test_expect_success 'setup some history and refs' ' git update-ref refs/odd/spot master ' +test_expect_success 'filtering with --points-at' ' + cat >expect <<-\EOF && + refs/heads/master + refs/odd/spot + refs/tags/three + EOF + git for-each-ref --format="%(refname)" --points-at=master >actual && + test_cmp expect actual +' + +test_expect_success 'check signed tags with --points-at' ' + sed -e "s/Z$//" >expect <<-\EOF && + refs/heads/side Z + refs/tags/four Z + refs/tags/signed-tag four + EOF + git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual && + test_cmp expect actual +' + test_done From 5afcb90560586765bf21fb09959a8b4497804639 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:11 +0530 Subject: [PATCH 028/539] ref-filter: add parse_opt_merge_filter() Add 'parse_opt_merge_filter()' to parse '--merged' and '--no-merged' options and write macros for the same. This is copied from 'builtin/branch.c' which will eventually be removed when we port 'branch.c' to use ref-filter APIs. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 4 ++++ ref-filter.c | 19 +++++++++++++++++++ ref-filter.h | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/builtin/branch.c b/builtin/branch.c index b42e5b6dbc7601..ddd90e6b1c7bf1 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -745,6 +745,10 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&newsection); } +/* + * This function is duplicated in ref-filter. It will eventually be removed + * when we port branch.c to use ref-filter APIs. + */ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) { merge_filter = ((opt->long_name[0] == 'n') diff --git a/ref-filter.c b/ref-filter.c index 2efe30a67e6d4e..4fe5a7a1f2706a 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1134,3 +1134,22 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) s->atom = parse_ref_filter_atom(arg, arg+len); return 0; } + +int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset) +{ + struct ref_filter *rf = opt->value; + unsigned char sha1[20]; + + rf->merge = starts_with(opt->long_name, "no") + ? REF_FILTER_MERGED_OMIT + : REF_FILTER_MERGED_INCLUDE; + + if (get_sha1(arg, sha1)) + die(_("malformed object name %s"), arg); + + rf->merge_commit = lookup_commit_reference_gently(sha1, 0); + if (!rf->merge_commit) + return opterror(opt, "must point to a commit", 0); + + return 0; +} diff --git a/ref-filter.h b/ref-filter.h index c2856b829570a5..443cfa7179290c 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -50,6 +50,15 @@ struct ref_filter_cbdata { struct ref_filter *filter; }; +/* Macros for checking --merged and --no-merged options */ +#define _OPT_MERGED_NO_MERGED(option, filter, h) \ + { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \ + PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \ + parse_opt_merge_filter, (intptr_t) "HEAD" \ + } +#define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h) +#define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h) + /* * API for filtering a set of refs. Based on the type of refs the user * has requested, we iterate through those refs and apply filters @@ -71,5 +80,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset); /* Default sort option based on refname */ struct ref_sorting *ref_default_sorting(void); +/* Function to parse --merged and --no-merged options */ +int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset); #endif /* REF_FILTER_H */ From 35257aa01203bae74f9fb856fb02c10c4b3836e6 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:12 +0530 Subject: [PATCH 029/539] ref-filter: implement '--merged' and '--no-merged' options In 'branch -l' we have '--merged' option which only lists refs (branches) merged into the named commit and '--no-merged' option which only lists refs (branches) not merged into the named commit. Implement these two options in ref-filter.{c,h} so that other commands can benefit from this. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 4 +++ ref-filter.c | 73 +++++++++++++++++++++++++++++++++++++++++++++--- ref-filter.h | 8 ++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index ddd90e6b1c7bf1..e63102e47bdb11 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -635,6 +635,10 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru cb.pattern = pattern; cb.ret = 0; for_each_rawref(append_ref, &cb); + /* + * The following implementation is currently duplicated in ref-filter. It + * will eventually be removed when we port branch.c to use ref-filter APIs. + */ if (merge_filter != NO_FILTER) { struct commit *filter; filter = lookup_commit_reference_gently(merge_filter_ref, 0); diff --git a/ref-filter.c b/ref-filter.c index 4fe5a7a1f2706a..0d7ec446dcebbd 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -9,6 +9,7 @@ #include "tag.h" #include "quote.h" #include "ref-filter.h" +#include "revision.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; @@ -898,6 +899,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, struct ref_filter_cbdata *ref_cbdata = cb_data; struct ref_filter *filter = ref_cbdata->filter; struct ref_array_item *ref; + struct commit *commit = NULL; if (flag & REF_BAD_NAME) { warning("ignoring ref with broken name %s", refname); @@ -910,12 +912,24 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname)) return 0; + /* + * A merge filter is applied on refs pointing to commits. Hence + * obtain the commit using the 'oid' available and discard all + * non-commits early. The actual filtering is done later. + */ + if (filter->merge_commit) { + commit = lookup_commit_reference_gently(oid->hash, 1); + if (!commit) + return 0; + } + /* * We do not open the object yet; sort may only need refname * to do its job and the resulting list may yet to be pruned * by maxcount logic. */ ref = new_ref_array_item(refname, oid->hash, flag); + ref->commit = commit; REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1); ref_cbdata->array->items[ref_cbdata->array->nr++] = ref; @@ -941,6 +955,50 @@ void ref_array_clear(struct ref_array *array) array->nr = array->alloc = 0; } +static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) +{ + struct rev_info revs; + int i, old_nr; + struct ref_filter *filter = ref_cbdata->filter; + struct ref_array *array = ref_cbdata->array; + struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr); + + init_revisions(&revs, NULL); + + for (i = 0; i < array->nr; i++) { + struct ref_array_item *item = array->items[i]; + add_pending_object(&revs, &item->commit->object, item->refname); + to_clear[i] = item->commit; + } + + filter->merge_commit->object.flags |= UNINTERESTING; + add_pending_object(&revs, &filter->merge_commit->object, ""); + + revs.limited = 1; + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + + old_nr = array->nr; + array->nr = 0; + + for (i = 0; i < old_nr; i++) { + struct ref_array_item *item = array->items[i]; + struct commit *commit = item->commit; + + int is_merged = !!(commit->object.flags & UNINTERESTING); + + if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE)) + array->items[array->nr++] = array->items[i]; + else + free_array_item(item); + } + + for (i = 0; i < old_nr; i++) + clear_commit_marks(to_clear[i], ALL_REV_FLAGS); + clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS); + free(to_clear); +} + /* * API for filtering a set of refs. Based on the type of refs the user * has requested, we iterate through those refs and apply filters @@ -950,17 +1008,24 @@ void ref_array_clear(struct ref_array *array) int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type) { struct ref_filter_cbdata ref_cbdata; + int ret = 0; ref_cbdata.array = array; ref_cbdata.filter = filter; + /* Simple per-ref filtering */ if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN)) - return for_each_rawref(ref_filter_handler, &ref_cbdata); + ret = for_each_rawref(ref_filter_handler, &ref_cbdata); else if (type & FILTER_REFS_ALL) - return for_each_ref(ref_filter_handler, &ref_cbdata); - else + ret = for_each_ref(ref_filter_handler, &ref_cbdata); + else if (type) die("filter_refs: invalid type"); - return 0; + + /* Filters that need revision walking */ + if (filter->merge_commit) + do_merge_filter(&ref_cbdata); + + return ret; } static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b) diff --git a/ref-filter.h b/ref-filter.h index 443cfa7179290c..f1151749a80227 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -31,6 +31,7 @@ struct ref_array_item { unsigned char objectname[20]; int flag; const char *symref; + struct commit *commit; struct atom_value *value; char refname[FLEX_ARRAY]; }; @@ -43,6 +44,13 @@ struct ref_array { struct ref_filter { const char **name_patterns; struct sha1_array points_at; + + enum { + REF_FILTER_MERGED_NONE = 0, + REF_FILTER_MERGED_INCLUDE, + REF_FILTER_MERGED_OMIT + } merge; + struct commit *merge_commit; }; struct ref_filter_cbdata { From 7c32834813bd6aee5ce5b2c4131d9f34249bc9e3 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:13 +0530 Subject: [PATCH 030/539] for-each-ref: add '--merged' and '--no-merged' options Add the '--merged' and '--no-merged' options provided by 'ref-filter'. The '--merged' option lets the user to only list refs merged into the named commit. The '--no-merged' option lets the user to only list refs not merged into the named commit. Add documentation and tests for the same. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 10 +++++++++- builtin/for-each-ref.c | 3 +++ t/t6302-for-each-ref-filter.sh | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index ff0283beca0c9f..2842195d110568 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git for-each-ref' [--count=] [--shell|--perl|--python|--tcl] [(--sort=)...] [--format=] [...] - [--points-at ] + [--points-at ] [(--merged | --no-merged) []] DESCRIPTION ----------- @@ -66,6 +66,14 @@ OPTIONS --points-at :: Only list refs which points at the given object. +--merged []:: + Only list refs whose tips are reachable from the + specified commit (HEAD if not specified). + +--no-merged []:: + Only list refs whose tips are not reachable from the + specified commit (HEAD if not specified). + FIELD NAMES ----------- diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index ae5419e0de74f9..75218506e40d91 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -8,6 +8,7 @@ static char const * const for_each_ref_usage[] = { N_("git for-each-ref [] []"), N_("git for-each-ref [--points-at ]"), + N_("git for-each-ref [(--merged | --no-merged) []]"), NULL }; @@ -38,6 +39,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), N_("print only refs which points at the given object"), parse_opt_object_name), + OPT_MERGED(&filter, N_("print only refs that are merged")), + OPT_NO_MERGED(&filter, N_("print only refs that are not merged")), OPT_END(), }; diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 98ff4d5e43b6f6..0e68dcea6597ee 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -43,4 +43,27 @@ test_expect_success 'check signed tags with --points-at' ' test_cmp expect actual ' +test_expect_success 'filtering with --merged' ' + cat >expect <<-\EOF && + refs/heads/master + refs/odd/spot + refs/tags/one + refs/tags/three + refs/tags/two + EOF + git for-each-ref --format="%(refname)" --merged=master >actual && + test_cmp expect actual +' + +test_expect_success 'filtering with --no-merged' ' + cat >expect <<-\EOF && + refs/heads/side + refs/tags/double-tag + refs/tags/four + refs/tags/signed-tag + EOF + git for-each-ref --format="%(refname)" --no-merged=master >actual && + test_cmp expect actual +' + test_done From 9d306b5a6077f026291df3c4a5968eaf95a92417 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:14 +0530 Subject: [PATCH 031/539] parse-option: rename parse_opt_with_commit() Rename parse_opt_with_commit() to parse_opt_commits() to show that it can be used to obtain a list of commits and is not constricted to usage of '--contains' option. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 4 ++-- builtin/tag.c | 4 ++-- parse-options-cb.c | 2 +- parse-options.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index e63102e47bdb11..ae9a0ebc666fa1 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -832,13 +832,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), N_("print only branches that contain the commit"), PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", + parse_opt_commits, (intptr_t)"HEAD", }, { OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), N_("print only branches that contain the commit"), PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t) "HEAD", + parse_opt_commits, (intptr_t) "HEAD", }, OPT__ABBREV(&abbrev), diff --git a/builtin/tag.c b/builtin/tag.c index 280981f573be99..7af45a0a90c11e 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -603,13 +603,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), N_("print only tags that contain the commit"), PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", + parse_opt_commits, (intptr_t)"HEAD", }, { OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), N_("print only tags that contain the commit"), PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", + parse_opt_commits, (intptr_t)"HEAD", }, { OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"), diff --git a/parse-options-cb.c b/parse-options-cb.c index de75411086c861..632f10f2026661 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -77,7 +77,7 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg, return 0; } -int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) +int parse_opt_commits(const struct option *opt, const char *arg, int unset) { unsigned char sha1[20]; struct commit *commit; diff --git a/parse-options.h b/parse-options.h index 36c71fedf14ed0..2ffd8571271241 100644 --- a/parse-options.h +++ b/parse-options.h @@ -221,7 +221,7 @@ extern int parse_opt_expiry_date_cb(const struct option *, const char *, int); extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_object_name(const struct option *, const char *, int); -extern int parse_opt_with_commit(const struct option *, const char *, int); +extern int parse_opt_commits(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); extern int parse_opt_string_list(const struct option *, const char *, int); extern int parse_opt_noop_cb(const struct option *, const char *, int); From f266c9163b34ec55e453d27e1ed246d67ee3ced0 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:15 +0530 Subject: [PATCH 032/539] parse-options.h: add macros for '--contains' option Add a macro for using the '--contains' option in parse-options.h also include an optional '--with' option macro which performs the same action as '--contains'. Make tag.c and branch.c use this new macro. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 14 ++------------ builtin/tag.c | 14 ++------------ parse-options.h | 7 +++++++ 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index ae9a0ebc666fa1..c443cd82516932 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -828,18 +828,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__COLOR(&branch_use_color, N_("use colored output")), OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"), REF_REMOTE_BRANCH), - { - OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), - N_("print only branches that contain the commit"), - PARSE_OPT_LASTARG_DEFAULT, - parse_opt_commits, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), - N_("print only branches that contain the commit"), - PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_commits, (intptr_t) "HEAD", - }, + OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")), + OPT_WITH(&with_commit, N_("print only branches that contain the commit")), OPT__ABBREV(&abbrev), OPT_GROUP(N_("Specific git-branch actions:")), diff --git a/builtin/tag.c b/builtin/tag.c index 7af45a0a90c11e..767162e3a9087a 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -595,22 +595,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), + OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")), + OPT_WITH(&with_commit, N_("print only tags that contain the commit")), { OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), PARSE_OPT_NONEG, parse_opt_sort }, - { - OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), - N_("print only tags that contain the commit"), - PARSE_OPT_LASTARG_DEFAULT, - parse_opt_commits, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), - N_("print only tags that contain the commit"), - PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_commits, (intptr_t)"HEAD", - }, { OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"), N_("print only tags of the object"), 0, parse_opt_object_name diff --git a/parse-options.h b/parse-options.h index 2ffd8571271241..6db7cb31cc90fa 100644 --- a/parse-options.h +++ b/parse-options.h @@ -243,5 +243,12 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int); OPT_COLOR_FLAG(0, "color", (var), (h)) #define OPT_COLUMN(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback } +#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \ + { OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \ + PARSE_OPT_LASTARG_DEFAULT | flag, \ + parse_opt_commits, (intptr_t) "HEAD" \ + } +#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0) +#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN) #endif From ee2bd06b0f735a00ce0216ca1d3391b13722d987 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:16 +0530 Subject: [PATCH 033/539] ref-filter: implement '--contains' option 'tag -l' and 'branch -l' have two different ways of finding out if a certain ref contains a commit. Implement both these methods in ref-filter and give the caller of ref-filter API the option to pick which implementation to be used. 'branch -l' uses 'is_descendant_of()' from commit.c which is left as the default implementation to be used. 'tag -l' uses a more specific algorithm since ffc4b80. This implementation is used whenever the 'with_commit_tag_algo' bit is set in 'struct ref_filter'. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/tag.c | 5 +++ ref-filter.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++- ref-filter.h | 3 ++ 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/builtin/tag.c b/builtin/tag.c index 767162e3a9087a..071d001655c754 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -86,6 +86,11 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) return 0; } +/* + * The entire code segment for supporting the --contains option has been + * copied over to ref-filter.{c,h}. This will be deleted evetually when + * we port tag.c to use ref-filter APIs. + */ enum contains_result { CONTAINS_UNKNOWN = -1, CONTAINS_NO = 0, diff --git a/ref-filter.c b/ref-filter.c index 0d7ec446dcebbd..409b94fcfba60b 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -818,6 +818,114 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom *v = &ref->value[atom]; } +enum contains_result { + CONTAINS_UNKNOWN = -1, + CONTAINS_NO = 0, + CONTAINS_YES = 1 +}; + +/* + * Mimicking the real stack, this stack lives on the heap, avoiding stack + * overflows. + * + * At each recursion step, the stack items points to the commits whose + * ancestors are to be inspected. + */ +struct contains_stack { + int nr, alloc; + struct contains_stack_entry { + struct commit *commit; + struct commit_list *parents; + } *contains_stack; +}; + +static int in_commit_list(const struct commit_list *want, struct commit *c) +{ + for (; want; want = want->next) + if (!hashcmp(want->item->object.sha1, c->object.sha1)) + return 1; + return 0; +} + +/* + * Test whether the candidate or one of its parents is contained in the list. + * Do not recurse to find out, though, but return -1 if inconclusive. + */ +static enum contains_result contains_test(struct commit *candidate, + const struct commit_list *want) +{ + /* was it previously marked as containing a want commit? */ + if (candidate->object.flags & TMP_MARK) + return 1; + /* or marked as not possibly containing a want commit? */ + if (candidate->object.flags & UNINTERESTING) + return 0; + /* or are we it? */ + if (in_commit_list(want, candidate)) { + candidate->object.flags |= TMP_MARK; + return 1; + } + + if (parse_commit(candidate) < 0) + return 0; + + return -1; +} + +static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack) +{ + ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc); + contains_stack->contains_stack[contains_stack->nr].commit = candidate; + contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents; +} + +static enum contains_result contains_tag_algo(struct commit *candidate, + const struct commit_list *want) +{ + struct contains_stack contains_stack = { 0, 0, NULL }; + int result = contains_test(candidate, want); + + if (result != CONTAINS_UNKNOWN) + return result; + + push_to_contains_stack(candidate, &contains_stack); + while (contains_stack.nr) { + struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1]; + struct commit *commit = entry->commit; + struct commit_list *parents = entry->parents; + + if (!parents) { + commit->object.flags |= UNINTERESTING; + contains_stack.nr--; + } + /* + * If we just popped the stack, parents->item has been marked, + * therefore contains_test will return a meaningful 0 or 1. + */ + else switch (contains_test(parents->item, want)) { + case CONTAINS_YES: + commit->object.flags |= TMP_MARK; + contains_stack.nr--; + break; + case CONTAINS_NO: + entry->parents = parents->next; + break; + case CONTAINS_UNKNOWN: + push_to_contains_stack(parents->item, &contains_stack); + break; + } + } + free(contains_stack.contains_stack); + return contains_test(candidate, want); +} + +static int commit_contains(struct ref_filter *filter, struct commit *commit) +{ + if (filter->with_commit_tag_algo) + return contains_tag_algo(commit, filter->with_commit); + return is_descendant_of(commit, filter->with_commit); +} + /* * Return 1 if the refname matches one of the patterns, otherwise 0. * A pattern can be path prefix (e.g. a refname "refs/heads/master" @@ -917,10 +1025,14 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, * obtain the commit using the 'oid' available and discard all * non-commits early. The actual filtering is done later. */ - if (filter->merge_commit) { + if (filter->merge_commit || filter->with_commit) { commit = lookup_commit_reference_gently(oid->hash, 1); if (!commit) return 0; + /* We perform the filtering for the '--contains' option */ + if (filter->with_commit && + !commit_contains(filter, commit)) + return 0; } /* diff --git a/ref-filter.h b/ref-filter.h index f1151749a80227..6bf27d8bc4a0b3 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -44,6 +44,7 @@ struct ref_array { struct ref_filter { const char **name_patterns; struct sha1_array points_at; + struct commit_list *with_commit; enum { REF_FILTER_MERGED_NONE = 0, @@ -51,6 +52,8 @@ struct ref_filter { REF_FILTER_MERGED_OMIT } merge; struct commit *merge_commit; + + unsigned int with_commit_tag_algo : 1; }; struct ref_filter_cbdata { From 4a71109aa442ba1a7045d36f6b148113c95ffc48 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Tue, 7 Jul 2015 21:36:17 +0530 Subject: [PATCH 034/539] for-each-ref: add '--contains' option Add the '--contains' option provided by 'ref-filter'. The '--contains' option lists only refs which contain the mentioned commit (HEAD if no commit is explicitly given). Add documentation and tests for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 5 +++++ builtin/for-each-ref.c | 2 ++ t/t6302-for-each-ref-filter.sh | 15 +++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 2842195d110568..e49d5782fc6f82 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git for-each-ref' [--count=] [--shell|--perl|--python|--tcl] [(--sort=)...] [--format=] [...] [--points-at ] [(--merged | --no-merged) []] + [--contains []] DESCRIPTION ----------- @@ -74,6 +75,10 @@ OPTIONS Only list refs whose tips are not reachable from the specified commit (HEAD if not specified). +--contains []:: + Only list tags which contain the specified commit (HEAD if not + specified). + FIELD NAMES ----------- diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 75218506e40d91..40f343b6a36fc7 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -9,6 +9,7 @@ static char const * const for_each_ref_usage[] = { N_("git for-each-ref [] []"), N_("git for-each-ref [--points-at ]"), N_("git for-each-ref [(--merged | --no-merged) []]"), + N_("git for-each-ref [--contains []]"), NULL }; @@ -41,6 +42,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) parse_opt_object_name), OPT_MERGED(&filter, N_("print only refs that are merged")), OPT_NO_MERGED(&filter, N_("print only refs that are not merged")), + OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")), OPT_END(), }; diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 0e68dcea6597ee..505a3601610be4 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -66,4 +66,19 @@ test_expect_success 'filtering with --no-merged' ' test_cmp expect actual ' +test_expect_success 'filtering with --contains' ' + cat >expect <<-\EOF && + refs/heads/master + refs/heads/side + refs/odd/spot + refs/tags/double-tag + refs/tags/four + refs/tags/signed-tag + refs/tags/three + refs/tags/two + EOF + git for-each-ref --format="%(refname)" --contains=two >actual && + test_cmp expect actual +' + test_done From fe67687bb1b38cbbdca4339caf14136b33e04783 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 29 Jun 2015 17:40:32 +0200 Subject: [PATCH 035/539] bisect: sanity check on terms This is currently only a defensive check since the only terms are bad/good and new/old, which pass it, but this is a preparation step for accepting user-supplied terms. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- git-bisect.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/git-bisect.sh b/git-bisect.sh index ea63223ab3b5d4..761ca6cca0ff7a 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -535,9 +535,42 @@ get_terms () { write_terms () { TERM_BAD=$1 TERM_GOOD=$2 + if test "$TERM_BAD" = "$TERM_GOOD" + then + die "$(gettext "please use two different terms")" + fi + check_term_format "$TERM_BAD" bad + check_term_format "$TERM_GOOD" good printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS" } +check_term_format () { + term=$1 + git check-ref-format refs/bisect/"$term" || + die "$(eval_gettext "'\$term' is not a valid term")" + case "$term" in + help|start|terms|skip|next|reset|visualize|replay|log|run) + die "$(eval_gettext "can't use the builtin command '\$term' as a term")" + ;; + bad|new) + if test "$2" != bad + then + # In theory, nothing prevents swapping + # completely good and bad, but this situation + # could be confusing and hasn't been tested + # enough. Forbid it for now. + die "$(eval_gettext "can't change the meaning of term '\$term'")" + fi + ;; + good|old) + if test "$2" != good + then + die "$(eval_gettext "can't change the meaning of term '\$term'")" + fi + ;; + esac +} + check_and_set_terms () { cmd="$1" case "$cmd" in From 21e5cfd8b3d35a702b19be6964b8809045dd6278 Mon Sep 17 00:00:00 2001 From: Antoine Delaite Date: Mon, 29 Jun 2015 17:40:33 +0200 Subject: [PATCH 036/539] bisect: add the terms old/new When not looking for a regression during a bisect but for a fix or a change in another given property, it can be confusing to use 'good' and 'bad'. This patch introduce `git bisect new` and `git bisect old` as an alternative to 'bad' and good': the commits which have a certain property must be marked as `new` and the ones which do not as `old`. The output will be the first commit after the change in the property. During a new/old bisect session you cannot use bad/good commands and vice-versa. Some commands are still not available for old/new: * git rev-list --bisect does not treat the revs/bisect/new and revs/bisect/old-SHA1 files. Old discussions: - http://thread.gmane.org/gmane.comp.version-control.git/86063 introduced bisect fix unfixed to find fix. - http://thread.gmane.org/gmane.comp.version-control.git/182398 discussion around bisect yes/no or old/new. - http://thread.gmane.org/gmane.comp.version-control.git/199758 last discussion and reviews New discussions: - http://thread.gmane.org/gmane.comp.version-control.git/271320 ( v2 1/7-4/7 ) - http://comments.gmane.org/gmane.comp.version-control.git/271343 ( v2 5/7-7/7 ) Signed-off-by: Antoine Delaite Signed-off-by: Louis Stuber Signed-off-by: Valentin Duperray Signed-off-by: Franck Jonas Signed-off-by: Lucien Kong Signed-off-by: Thomas Nguy Signed-off-by: Huynh Khoi Nguyen Nguyen Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-bisect.txt | 58 ++++++++++++++++++++++++++++++++++-- bisect.c | 11 +++++-- git-bisect.sh | 26 ++++++++++------ t/t6030-bisect-porcelain.sh | 38 +++++++++++++++++++++++ 4 files changed, 119 insertions(+), 14 deletions(-) diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index e97f2de21bdc58..abaf462273c1ad 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -17,8 +17,8 @@ The command takes various subcommands, and different options depending on the subcommand: git bisect start [--no-checkout] [ [...]] [--] [...] - git bisect bad [] - git bisect good [...] + git bisect (bad|new) [] + git bisect (good|old) [...] git bisect skip [(|)...] git bisect reset [] git bisect visualize @@ -36,6 +36,13 @@ whether the selected commit is "good" or "bad". It continues narrowing down the range until it finds the exact commit that introduced the change. +In fact, `git bisect` can be used to find the commit that changed +*any* property of your project; e.g., the commit that fixed a bug, or +the commit that caused a benchmark's performance to improve. To +support this more general usage, the terms "old" and "new" can be used +in place of "good" and "bad". See +section "Alternate terms" below for more information. + Basic bisect commands: start, bad, good ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -111,6 +118,45 @@ bad revision, while `git bisect reset HEAD` will leave you on the current bisection commit and avoid switching commits at all. +Alternate terms +~~~~~~~~~~~~~~~ + +Sometimes you are not looking for the commit that introduced a +breakage, but rather for a commit that caused a change between some +other "old" state and "new" state. For example, you might be looking +for the commit that introduced a particular fix. Or you might be +looking for the first commit in which the source-code filenames were +finally all converted to your company's naming standard. Or whatever. + +In such cases it can be very confusing to use the terms "good" and +"bad" to refer to "the state before the change" and "the state after +the change". So instead, you can use the terms "old" and "new", +respectively, in place of "good" and "bad". (But note that you cannot +mix "good" and "bad" with "old" and "new" in a single session.) + +In this more general usage, you provide `git bisect` with a "new" +commit has some property and an "old" commit that doesn't have that +property. Each time `git bisect` checks out a commit, you test if that +commit has the property. If it does, mark the commit as "new"; +otherwise, mark it as "old". When the bisection is done, `git bisect` +will report which commit introduced the property. + +To use "old" and "new" instead of "good" and bad, you must run `git +bisect start` without commits as argument and then run the following +commands to add the commits: + +------------------------------------------------ +git bisect old [] +------------------------------------------------ + +to indicate that a commit was before the sought change, or + +------------------------------------------------ +git bisect new [...] +------------------------------------------------ + +to indicate that it was after. + Bisect visualize ~~~~~~~~~~~~~~~~ @@ -387,6 +433,14 @@ In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit has at least one parent whose reachable graph is fully traversable in the sense required by 'git pack objects'. +* Look for a fix instead of a regression in the code ++ +------------ +$ git bisect start +$ git bisect new HEAD # current commit is marked as new +$ git bisect old HEAD~10 # the tenth commit from now is marked as old +------------ + Getting help ~~~~~~~~~~~~ diff --git a/bisect.c b/bisect.c index 857cf59aa3e33a..f7292cbac4def8 100644 --- a/bisect.c +++ b/bisect.c @@ -746,6 +746,11 @@ static void handle_bad_merge_base(void) "This means the bug has been fixed " "between %s and [%s].\n", bad_hex, bad_hex, good_hex); + } else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) { + fprintf(stderr, "The merge base %s is new.\n" + "The property has changed " + "between %s and [%s].\n", + bad_hex, bad_hex, good_hex); } else { fprintf(stderr, "The merge base %s is %s.\n" "This means the first '%s' commit is " @@ -778,11 +783,11 @@ static void handle_skipped_merge_base(const unsigned char *mb) } /* - * "check_merge_bases" checks that merge bases are not "bad". + * "check_merge_bases" checks that merge bases are not "bad" (or "new"). * - * - If one is "bad", it means the user assumed something wrong + * - If one is "bad" (or "new"), it means the user assumed something wrong * and we must exit with a non 0 error code. - * - If one is "good", that's good, we have nothing to do. + * - If one is "good" (or "old"), that's good, we have nothing to do. * - If one is "skipped", we can't know but we should warn. * - If we don't know, we should check it out and ask the user to test. */ diff --git a/git-bisect.sh b/git-bisect.sh index 761ca6cca0ff7a..d78b043b11dabe 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,14 +1,16 @@ #!/bin/sh -USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]' +USAGE='[help|start|bad|good|new|old|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect help print this long help message. git bisect start [--no-checkout] [ [...]] [--] [...] reset bisect state and start bisection. -git bisect bad [] - mark a known-bad revision. -git bisect good [...] - mark ... known-good revisions. +git bisect (bad|new) [] + mark a known-bad revision/ + a revision after change in a given property. +git bisect (good|old) [...] + mark ... known-good revisions/ + revisions before change in a given property. git bisect skip [(|)...] mark ... untestable revisions. git bisect next @@ -294,7 +296,7 @@ bisect_next_check() { false ;; t,,"$TERM_GOOD") - # have bad but not good. we could bisect although + # have bad (or new) but not good (or old). we could bisect although # this is less optimum. eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2 if test -t 0 @@ -587,14 +589,20 @@ check_and_set_terms () { write_terms bad good fi ;; + new|old) + if ! test -s "$GIT_DIR/BISECT_TERMS" + then + write_terms new old + fi + ;; esac ;; esac } bisect_voc () { case "$1" in - bad) echo "bad" ;; - good) echo "good" ;; + bad) echo "bad|new" ;; + good) echo "good|old" ;; esac } @@ -610,7 +618,7 @@ case "$#" in git bisect -h ;; start) bisect_start "$@" ;; - bad|good|"$TERM_BAD"|"$TERM_GOOD") + bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD") bisect_state "$cmd" "$@" ;; skip) bisect_skip "$@" ;; diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 9e2c20374732d7..983c5033c9c6e0 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -759,4 +759,42 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' ' git bisect reset ' +test_expect_success 'bisect starts with only one new' ' + git bisect reset && + git bisect start && + git bisect new $HASH4 && + git bisect next +' + +test_expect_success 'bisect does not start with only one old' ' + git bisect reset && + git bisect start && + git bisect old $HASH1 && + test_must_fail git bisect next +' + +test_expect_success 'bisect start with one new and old' ' + git bisect reset && + git bisect start && + git bisect old $HASH1 && + git bisect new $HASH4 && + git bisect new && + git bisect new >bisect_result && + grep "$HASH2 is the first new commit" bisect_result && + git bisect log >log_to_replay.txt && + git bisect reset +' + +test_expect_success 'bisect replay with old and new' ' + git bisect replay log_to_replay.txt >bisect_result && + grep "$HASH2 is the first new commit" bisect_result && + git bisect reset +' + +test_expect_success 'bisect cannot mix old/new and good/bad' ' + git bisect start && + git bisect bad $HASH4 && + test_must_fail git bisect old $HASH1 +' + test_done From 21b55e33695f47f3e2616d178ab1e06743bfef66 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 29 Jun 2015 17:40:34 +0200 Subject: [PATCH 037/539] bisect: add 'git bisect terms' to view the current terms Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-bisect.txt | 10 +++++++++ git-bisect.sh | 39 +++++++++++++++++++++++++++++++++++- t/t6030-bisect-porcelain.sh | 20 ++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index abaf462273c1ad..4dd62958098f4b 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -19,6 +19,7 @@ on the subcommand: git bisect start [--no-checkout] [ [...]] [--] [...] git bisect (bad|new) [] git bisect (good|old) [...] + git bisect terms [--term-good | --term-bad] git bisect skip [(|)...] git bisect reset [] git bisect visualize @@ -157,6 +158,15 @@ git bisect new [...] to indicate that it was after. +To get a reminder of the currently used terms, use + +------------------------------------------------ +git bisect terms +------------------------------------------------ + +You can get just the old (respectively new) term with `git bisect term +--term-old` or `git bisect term --term-good`. + Bisect visualize ~~~~~~~~~~~~~~~~ diff --git a/git-bisect.sh b/git-bisect.sh index d78b043b11dabe..89255a333f4fe3 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[help|start|bad|good|new|old|skip|next|reset|visualize|replay|log|run]' +USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect help print this long help message. git bisect start [--no-checkout] [ [...]] [--] [...] @@ -11,6 +11,8 @@ git bisect (bad|new) [] git bisect (good|old) [...] mark ... known-good revisions/ revisions before change in a given property. +git bisect terms [--term-good | --term-bad] + show the terms used for old and new commits (default: bad, good) git bisect skip [(|)...] mark ... untestable revisions. git bisect next @@ -453,6 +455,8 @@ bisect_replay () { eval "$cmd" ;; "$TERM_GOOD"|"$TERM_BAD"|skip) bisect_write "$command" "$rev" ;; + terms) + bisect_terms $rev ;; *) die "$(gettext "?? what are you talking about?")" ;; esac @@ -606,6 +610,37 @@ bisect_voc () { esac } +bisect_terms () { + get_terms + if ! test -s "$GIT_DIR/BISECT_TERMS" + then + die "$(gettext "no terms defined")" + fi + case "$#" in + 0) + gettextln "Your current terms are $TERM_GOOD for the old state +and $TERM_BAD for the new state." + ;; + 1) + arg=$1 + case "$arg" in + --term-good|--term-old) + printf '%s\n' "$TERM_GOOD" + ;; + --term-bad|--term-new) + printf '%s\n' "$TERM_BAD" + ;; + *) + die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'. +Supported options are: --term-good|--term-old and --term-bad|--term-new.")" + ;; + esac + ;; + *) + usage ;; + esac +} + case "$#" in 0) usage ;; @@ -635,6 +670,8 @@ case "$#" in bisect_log ;; run) bisect_run "$@" ;; + terms) + bisect_terms "$@" ;; *) usage ;; esac diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 983c5033c9c6e0..93934886508a09 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -797,4 +797,24 @@ test_expect_success 'bisect cannot mix old/new and good/bad' ' test_must_fail git bisect old $HASH1 ' +test_expect_success 'bisect terms needs 0 or 1 argument' ' + git bisect reset && + test_must_fail git bisect terms only-one && + test_must_fail git bisect terms 1 2 && + test_must_fail git bisect terms 2>actual && + echo "no terms defined" >expected && + test_cmp expected actual +' + +test_expect_success 'bisect terms shows good/bad after start' ' + git bisect reset && + git bisect start HEAD $HASH1 && + git bisect terms --term-good >actual && + echo good >expected && + test_cmp expected actual && + git bisect terms --term-bad >actual && + echo bad >expected && + test_cmp expected actual +' + test_done From 06e6a745064c4f2f827177f6d92f4b9adb018200 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 29 Jun 2015 17:40:35 +0200 Subject: [PATCH 038/539] bisect: allow setting any user-specified in 'git bisect start' This allows a natural user-interface when looking for any change in the code, not just regression. For example: git bisect start --term-old fast --term-new slow git bisect fast git bisect slow ... There were several proposed user-interfaces for this feature. This patch implements it as options to 'git bisect start' for the following reasons: * By construction, the terms will be valid for one and only one bisection. * Unlike positional arguments, using named options avoid having to remember an order. * We can combine user-defined terms and passing old/new commits as argument to "git bisect start". * The implementation is relatively simple. See previous discussions: http://mid.gmane.org/1435337896-20709-3-git-send-email-Matthieu.Moy@imag.fr Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-bisect.txt | 37 ++++++++++++++++- git-bisect.sh | 21 +++++++++- t/t6030-bisect-porcelain.sh | 77 ++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 4dd62958098f4b..2044fe6820e050 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -16,7 +16,8 @@ DESCRIPTION The command takes various subcommands, and different options depending on the subcommand: - git bisect start [--no-checkout] [ [...]] [--] [...] + git bisect start [--term-{old,good}= --term-{new,bad}=] + [--no-checkout] [ [...]] [--] [...] git bisect (bad|new) [] git bisect (good|old) [...] git bisect terms [--term-good | --term-bad] @@ -41,7 +42,7 @@ In fact, `git bisect` can be used to find the commit that changed *any* property of your project; e.g., the commit that fixed a bug, or the commit that caused a benchmark's performance to improve. To support this more general usage, the terms "old" and "new" can be used -in place of "good" and "bad". See +in place of "good" and "bad", or you can choose your own terms. See section "Alternate terms" below for more information. Basic bisect commands: start, bad, good @@ -167,6 +168,31 @@ git bisect terms You can get just the old (respectively new) term with `git bisect term --term-old` or `git bisect term --term-good`. +If you would like to use your own terms instead of "bad"/"good" or +"new"/"old", you can choose any names you like (except existing bisect +subcommands like `reset`, `start`, ...) by starting the +bisection using + +------------------------------------------------ +git bisect start --term-old --term-new +------------------------------------------------ + +For example, if you are looking for a commit that introduced a +performance regression, you might use + +------------------------------------------------ +git bisect start --term-old fast --term-new slow +------------------------------------------------ + +Or if you are looking for the commit that fixed a bug, you might use + +------------------------------------------------ +git bisect start --term-new fixed --term-old broken +------------------------------------------------ + +Then, use `git bisect ` and `git bisect ` instead +of `git bisect good` and `git bisect bad` to mark commits. + Bisect visualize ~~~~~~~~~~~~~~~~ @@ -450,6 +476,13 @@ $ git bisect start $ git bisect new HEAD # current commit is marked as new $ git bisect old HEAD~10 # the tenth commit from now is marked as old ------------ ++ +or: +------------ +$ git bisect start --term-old broken --term-new fixed +$ git bisect fixed +$ git bisect broken HEAD~10 +------------ Getting help ~~~~~~~~~~~~ diff --git a/git-bisect.sh b/git-bisect.sh index 89255a333f4fe3..5d1cb00d86b370 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -3,7 +3,8 @@ USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect help print this long help message. -git bisect start [--no-checkout] [ [...]] [--] [...] +git bisect start [--term-{old,good}= --term-{new,bad}=] + [--no-checkout] [ [...]] [--] [...] reset bisect state and start bisection. git bisect (bad|new) [] mark a known-bad revision/ @@ -99,6 +100,24 @@ bisect_start() { --no-checkout) mode=--no-checkout shift ;; + --term-good|--term-old) + shift + must_write_terms=1 + TERM_GOOD=$1 + shift ;; + --term-good=*|--term-old=*) + must_write_terms=1 + TERM_GOOD=${1#*=} + shift ;; + --term-bad|--term-new) + shift + must_write_terms=1 + TERM_BAD=$1 + shift ;; + --term-bad=*|--term-new=*) + must_write_terms=1 + TERM_BAD=${1#*=} + shift ;; --*) die "$(eval_gettext "unrecognised option: '\$arg'")" ;; *) diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 93934886508a09..e74662ba5c638d 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -817,4 +817,81 @@ test_expect_success 'bisect terms shows good/bad after start' ' test_cmp expected actual ' +test_expect_success 'bisect start with one term1 and term2' ' + git bisect reset && + git bisect start --term-old term2 --term-new term1 && + git bisect term2 $HASH1 && + git bisect term1 $HASH4 && + git bisect term1 && + git bisect term1 >bisect_result && + grep "$HASH2 is the first term1 commit" bisect_result && + git bisect log >log_to_replay.txt && + git bisect reset +' + +test_expect_success 'bisect replay with term1 and term2' ' + git bisect replay log_to_replay.txt >bisect_result && + grep "$HASH2 is the first term1 commit" bisect_result && + git bisect reset +' + +test_expect_success 'bisect start term1 term2' ' + git bisect reset && + git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 && + git bisect term1 && + git bisect term1 >bisect_result && + grep "$HASH2 is the first term1 commit" bisect_result && + git bisect log >log_to_replay.txt && + git bisect reset +' + +test_expect_success 'bisect cannot mix terms' ' + git bisect reset && + git bisect start --term-good term1 --term-bad term2 $HASH4 $HASH1 && + test_must_fail git bisect a && + test_must_fail git bisect b && + test_must_fail git bisect bad && + test_must_fail git bisect good && + test_must_fail git bisect new && + test_must_fail git bisect old +' + +test_expect_success 'bisect terms rejects invalid terms' ' + git bisect reset && + test_must_fail git bisect start --term-good invalid..term && + test_must_fail git bisect terms --term-bad invalid..term && + test_must_fail git bisect terms --term-good bad && + test_must_fail git bisect terms --term-good old && + test_must_fail git bisect terms --term-good skip && + test_must_fail git bisect terms --term-good reset && + test_path_is_missing .git/BISECT_TERMS +' + +test_expect_success 'bisect start --term-* does store terms' ' + git bisect reset && + git bisect start --term-bad=one --term-good=two && + git bisect terms >actual && + cat <<-EOF >expected && + Your current terms are two for the old state + and one for the new state. + EOF + test_cmp expected actual && + git bisect terms --term-bad >actual && + echo one >expected && + test_cmp expected actual && + git bisect terms --term-good >actual && + echo two >expected && + test_cmp expected actual +' + +test_expect_success 'bisect start takes options and revs in any order' ' + git bisect reset && + git bisect start --term-good one $HASH4 \ + --term-good two --term-bad bad-term \ + $HASH1 --term-good three -- && + (git bisect terms --term-bad && git bisect terms --term-good) >actual && + printf "%s\n%s\n" bad-term three >expected && + test_cmp expected actual +' + test_done From 094c7e635287e999f382681b0749ce7711e5ce7d Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 12 Aug 2015 19:43:01 +0200 Subject: [PATCH 039/539] prune: close directory earlier during loose-object directory traversal 27e1e22d (prune: factor out loose-object directory traversal, 2014-10-16) introduced a new function for_each_loose_file_in_objdir() with a helper for_each_file_in_obj_subdir(). The latter calls callbacks for each file found during a directory traversal and finally also a callback for the directory itself. git-prune uses the function to clean up the object directory. In particular, in the directory callback it calls rmdir(). On Windows XP, this rmdir call fails, because the directory is still open while the callback is called. Close the directory before calling the callback. Signed-off-by: Johannes Sixt Acked-by: Jeff King Signed-off-by: Junio C Hamano --- sha1_file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index bf1bdbcdf5e4f2..7ea7a93359312c 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -3356,12 +3356,12 @@ static int for_each_file_in_obj_subdir(int subdir_nr, break; } } - strbuf_setlen(path, baselen); + closedir(dir); + strbuf_setlen(path, baselen); if (!r && subdir_cb) r = subdir_cb(subdir_nr, path->buf, data); - closedir(dir); return r; } From 0a489b0680b841d3e7714be53b263ff190c39193 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 13 Aug 2015 13:02:52 -0500 Subject: [PATCH 040/539] prepare_packed_git(): refactor garbage reporting in pack directory The hook to report "garbage" files in $GIT_OBJECT_DIRECTORY/pack/ could be generic but is too specific to count-object's needs. Move the part to produce human-readable messages to count-objects, and refine the interface to callback with the "bits" with values defined in the cache.h header file, so that other callers (e.g. prune) can later use the same mechanism to enumerate different kinds of garbage files and do something intelligent about them, other than reporting in textual messages. Signed-off-by: Junio C Hamano --- builtin/count-objects.c | 26 ++++++++++++++++++++++++-- cache.h | 7 +++++-- path.c | 2 +- sha1_file.c | 23 ++++++----------------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/builtin/count-objects.c b/builtin/count-objects.c index ad0c79954aa0df..ba9291944f7752 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -15,9 +15,31 @@ static int verbose; static unsigned long loose, packed, packed_loose; static off_t loose_size; -static void real_report_garbage(const char *desc, const char *path) +static const char *bits_to_msg(unsigned seen_bits) +{ + switch (seen_bits) { + case 0: + return "no corresponding .idx or .pack"; + case PACKDIR_FILE_GARBAGE: + return "garbage found"; + case PACKDIR_FILE_PACK: + return "no corresponding .idx"; + case PACKDIR_FILE_IDX: + return "no corresponding .pack"; + case PACKDIR_FILE_PACK|PACKDIR_FILE_IDX: + default: + return NULL; + } +} + +static void real_report_garbage(unsigned seen_bits, const char *path) { struct stat st; + const char *desc = bits_to_msg(seen_bits); + + if (!desc) + return; + if (!stat(path, &st)) size_garbage += st.st_size; warning("%s: %s", desc, path); @@ -27,7 +49,7 @@ static void real_report_garbage(const char *desc, const char *path) static void loose_garbage(const char *path) { if (verbose) - report_garbage("garbage found", path); + report_garbage(PACKDIR_FILE_GARBAGE, path); } static int count_loose(const unsigned char *sha1, const char *path, void *data) diff --git a/cache.h b/cache.h index 4f554664c5bd06..c6de1e9c0e0387 100644 --- a/cache.h +++ b/cache.h @@ -1255,8 +1255,11 @@ struct pack_entry { extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); -/* A hook for count-objects to report invalid files in pack directory */ -extern void (*report_garbage)(const char *desc, const char *path); +/* A hook to report invalid files in pack directory */ +#define PACKDIR_FILE_PACK 1 +#define PACKDIR_FILE_IDX 2 +#define PACKDIR_FILE_GARBAGE 4 +extern void (*report_garbage)(unsigned seen_bits, const char *path); extern void prepare_packed_git(void); extern void reprepare_packed_git(void); diff --git a/path.c b/path.c index 10f4cbf6b78607..75ec2363fdd94a 100644 --- a/path.c +++ b/path.c @@ -143,7 +143,7 @@ void report_linked_checkout_garbage(void) strbuf_setlen(&sb, len); strbuf_addstr(&sb, path); if (file_exists(sb.buf)) - report_garbage("unused in linked checkout", sb.buf); + report_garbage(PACKDIR_FILE_GARBAGE, sb.buf); } strbuf_release(&sb); } diff --git a/sha1_file.c b/sha1_file.c index 1cee4384225fb9..0c0b6529490a7d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1183,27 +1183,16 @@ void install_packed_git(struct packed_git *pack) packed_git = pack; } -void (*report_garbage)(const char *desc, const char *path); +void (*report_garbage)(unsigned seen_bits, const char *path); static void report_helper(const struct string_list *list, int seen_bits, int first, int last) { - const char *msg; - switch (seen_bits) { - case 0: - msg = "no corresponding .idx or .pack"; - break; - case 1: - msg = "no corresponding .idx"; - break; - case 2: - msg = "no corresponding .pack"; - break; - default: + if (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX)) return; - } + for (; first < last; first++) - report_garbage(msg, list->items[first].string); + report_garbage(seen_bits, list->items[first].string); } static void report_pack_garbage(struct string_list *list) @@ -1226,7 +1215,7 @@ static void report_pack_garbage(struct string_list *list) if (baselen == -1) { const char *dot = strrchr(path, '.'); if (!dot) { - report_garbage("garbage found", path); + report_garbage(PACKDIR_FILE_GARBAGE, path); continue; } baselen = dot - path + 1; @@ -1298,7 +1287,7 @@ static void prepare_packed_git_one(char *objdir, int local) ends_with(de->d_name, ".keep")) string_list_append(&garbage, path.buf); else - report_garbage("garbage found", path.buf); + report_garbage(PACKDIR_FILE_GARBAGE, path.buf); } closedir(dir); report_pack_garbage(&garbage); From 62a3c4848ec4d51e3a9f1a6be6ad0ef75a7df67e Mon Sep 17 00:00:00 2001 From: Luke Diamand Date: Thu, 27 Aug 2015 08:18:56 +0100 Subject: [PATCH 041/539] git-p4: failing test for ignoring invalid p4 labels When importing a label which references a commit that git-p4 does not know about, git-p4 should skip it and go on to process other labels that can be imported. Instead it crashes when attempting to find the missing commit in the git history. This test demonstrates the problem. Signed-off-by: Luke Diamand Signed-off-by: Junio C Hamano --- t/t9811-git-p4-label-import.sh | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh index 095238fffe2757..22d1fd3e3e7608 100755 --- a/t/t9811-git-p4-label-import.sh +++ b/t/t9811-git-p4-label-import.sh @@ -214,6 +214,51 @@ test_expect_success 'use git config to enable import/export of tags' ' ) ' +p4_head_revision() { + p4 changes -m 1 "$@" | awk '{print $2}' +} + +# Importing a label that references a P4 commit that +# has not been seen. The presence of a label on a commit +# we haven't seen should not cause git-p4 to fail. It should +# merely skip that label, and still import other labels. +test_expect_failure 'importing labels with missing revisions' ' + test_when_finished cleanup_git && + ( + rm -fr "$cli" "$git" && + mkdir "$cli" && + P4CLIENT=missing-revision && + client_view "//depot/missing-revision/... //missing-revision/..." && + cd "$cli" && + >f1 && p4 add f1 && p4 submit -d "start" && + + p4 tag -l TAG_S0 ... && + + >f2 && p4 add f2 && p4 submit -d "second" && + + startrev=$(p4_head_revision //depot/missing-revision/...) && + + >f3 && p4 add f3 && p4 submit -d "third" && + + p4 edit f2 && date >f2 && p4 submit -d "change" f2 && + + endrev=$(p4_head_revision //depot/missing-revision/...) && + + p4 tag -l TAG_S1 ... && + + # we should skip TAG_S0 since it is before our startpoint, + # but pick up TAG_S1. + + git p4 clone --dest="$git" --import-labels -v \ + //depot/missing-revision/...@$startrev,$endrev && + ( + cd "$git" && + git rev-parse TAG_S1 && + ! git rev-parse TAG_S0 + ) + ) +' + test_expect_success 'kill p4d' ' kill_p4d From 9ab1cfe505d43215a61dc5012632dde66fe109db Mon Sep 17 00:00:00 2001 From: Luke Diamand Date: Thu, 27 Aug 2015 08:18:57 +0100 Subject: [PATCH 042/539] git-p4: do not terminate creating tag for unknown commit If p4 reports a tag for a commit that git-p4 does not know about (e.g. because it references a P4 changelist that was imported prior to the point at which the repo was cloned into git), make sure that the error is correctly caught and handled. rather than just crashing. Signed-off-by: Luke Diamand Signed-off-by: Junio C Hamano --- git-p4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-p4.py b/git-p4.py index 073f87bbfdc1dc..a62611a9191cf6 100755 --- a/git-p4.py +++ b/git-p4.py @@ -2494,9 +2494,9 @@ def importP4Labels(self, stream, p4Labels): # find the corresponding git commit; take the oldest commit changelist = int(change['change']) gitCommit = read_pipe(["git", "rev-list", "--max-count=1", - "--reverse", ":/\[git-p4:.*change = %d\]" % changelist]) + "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) if len(gitCommit) == 0: - print "could not find git commit for changelist %d" % changelist + print "importing label %s: could not find git commit for changelist %d" % (name, changelist) else: gitCommit = gitCommit.strip() commitFound = True From b43702ac56e602d5163ef662fb9caf382da90b94 Mon Sep 17 00:00:00 2001 From: Luke Diamand Date: Thu, 27 Aug 2015 08:18:58 +0100 Subject: [PATCH 043/539] git-p4: fix P4 label import for unprocessed commits With --detect-labels enabled, git-p4 will try to create tags using git fast-import by writing a "tag" clause to the fast-import stream. If the commit that the tag references has not yet actually been processed by fast-import, then the tag can't be created and git-p4 fails to import the P4 label. Teach git-p4 to use fast-import "marks" when creating tags which reference commits created during the current run of the program. Commits created before the current run are still referenced in the old way using a normal git commit. Signed-off-by: Luke Diamand Signed-off-by: Junio C Hamano --- git-p4.py | 25 +++++++++++++++++-------- t/t9811-git-p4-label-import.sh | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/git-p4.py b/git-p4.py index a62611a9191cf6..2018011ae574bf 100755 --- a/git-p4.py +++ b/git-p4.py @@ -2322,8 +2322,11 @@ def make_email(self, userid): else: return "%s " % userid - # Stream a p4 tag def streamTag(self, gitStream, labelName, labelDetails, commit, epoch): + """ Stream a p4 tag. + commit is either a git commit, or a fast-import mark, ":" + """ + if verbose: print "writing tag %s for commit %s" % (labelName, commit) gitStream.write("tag %s\n" % labelName) @@ -2374,7 +2377,7 @@ def commit(self, details, files, branch, parent = ""): self.clientSpecDirs.update_client_spec_path_cache(files) self.gitStream.write("commit %s\n" % branch) -# gitStream.write("mark :%s\n" % details["change"]) + self.gitStream.write("mark :%s\n" % details["change"]) self.committedChanges.add(int(details["change"])) committer = "" if author not in self.users: @@ -2493,13 +2496,19 @@ def importP4Labels(self, stream, p4Labels): if change.has_key('change'): # find the corresponding git commit; take the oldest commit changelist = int(change['change']) - gitCommit = read_pipe(["git", "rev-list", "--max-count=1", - "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) - if len(gitCommit) == 0: - print "importing label %s: could not find git commit for changelist %d" % (name, changelist) - else: - gitCommit = gitCommit.strip() + if changelist in self.committedChanges: + gitCommit = ":%d" % changelist # use a fast-import mark commitFound = True + else: + gitCommit = read_pipe(["git", "rev-list", "--max-count=1", + "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) + if len(gitCommit) == 0: + print "importing label %s: could not find git commit for changelist %d" % (name, changelist) + else: + commitFound = True + gitCommit = gitCommit.strip() + + if commitFound: # Convert from p4 time format try: tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S") diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh index 22d1fd3e3e7608..decb66ba308715 100755 --- a/t/t9811-git-p4-label-import.sh +++ b/t/t9811-git-p4-label-import.sh @@ -222,7 +222,7 @@ p4_head_revision() { # has not been seen. The presence of a label on a commit # we haven't seen should not cause git-p4 to fail. It should # merely skip that label, and still import other labels. -test_expect_failure 'importing labels with missing revisions' ' +test_expect_success 'importing labels with missing revisions' ' test_when_finished cleanup_git && ( rm -fr "$cli" "$git" && From 3086c064fbbf3c4d086f9d7c303d9aa76f5204b2 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Sun, 30 Aug 2015 00:25:57 +0900 Subject: [PATCH 044/539] stash: allow "stash show" diff output configurable Some users might want to see diff (patch) output always rather than diffstat when [s]he runs 'git stash show'. Although this can be done with adding -p option, users are too lazy to type extra three keys. Add two variables that control to show diffstat and patch output respectively. The stash.showStat is for diffstat and default is true. The stat.showPatch is for the patch output and default is false. Signed-off-by: Namhyung Kim Signed-off-by: Junio C Hamano --- Documentation/config.txt | 10 ++++++++++ Documentation/git-stash.txt | 2 ++ git-stash.sh | 20 +++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 43bb53c0477276..c2f08aa18b9762 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2476,6 +2476,16 @@ status.submoduleSummary:: submodule summary' command, which shows a similar output but does not honor these settings. +stash.showPatch:: + If this is set to true, the `git stash show` command without an + option will show the stash in patch form. Defaults to false. + See description of 'show' command in linkgit:git-stash[1]. + +stash.showStat:: + If this is set to true, the `git stash show` command without an + option will show diffstat of the stash. Defaults to true. + See description of 'show' command in linkgit:git-stash[1]. + submodule..path:: submodule..url:: The path within this project and URL for a submodule. These diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 375213fe463fa3..92df596e5fe975 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -95,6 +95,8 @@ show []:: shows the latest one. By default, the command shows the diffstat, but it will accept any format known to 'git diff' (e.g., `git stash show -p stash@{1}` to view the second most recent stash in patch form). + You can use stash.showStat and/or stash.showPatch config variables + to change the default behavior. pop [--index] [-q|--quiet] []:: diff --git a/git-stash.sh b/git-stash.sh index 8e9e2cd7d5697c..92bc0e1ad82129 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -307,7 +307,25 @@ show_stash () { ALLOW_UNKNOWN_FLAGS=t assert_stash_like "$@" - git diff ${FLAGS:---stat} $b_commit $w_commit + if test -z "$FLAGS" + then + if test "$(git config --bool stash.showStat || echo true)" = "true" + then + FLAGS=--stat + fi + + if test "$(git config --bool stash.showPatch || echo false)" = "true" + then + FLAGS=${FLAGS}${FLAGS:+ }-p + fi + + if test -z "$FLAGS" + then + return 0 + fi + fi + + git diff ${FLAGS} $b_commit $w_commit } show_help () { From 0701530c2663f82ff4b678de27db4c61f8b3092f Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 31 Aug 2015 22:13:09 -0400 Subject: [PATCH 045/539] refs: clean up common_list Instead of common_list having formatting like ! and /, use a struct to hold common_list data in a structured form. We don't use 'exclude' yet; instead, we keep the old codepath that handles info/sparse-checkout and logs/HEAD. Later, we will use exclude. [jc: with "make common_list[] static" clean-up from Ramsay squashed in] Signed-off-by: David Turner Signed-off-by: Junio C Hamano --- path.c | 58 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/path.c b/path.c index 95acbafa6883b4..2215ab5bf781b0 100644 --- a/path.c +++ b/path.c @@ -91,35 +91,51 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir) buf->buf[newlen] = '/'; } -static const char *common_list[] = { - "/branches", "/hooks", "/info", "!/logs", "/lost-found", - "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn", - "config", "!gc.pid", "packed-refs", "shallow", - NULL +struct common_dir { + /* Not considered garbage for report_linked_checkout_garbage */ + unsigned ignore_garbage:1; + unsigned is_dir:1; + /* Not common even though its parent is */ + unsigned exclude:1; + const char *dirname; +}; + +static struct common_dir common_list[] = { + { 0, 1, 0, "branches" }, + { 0, 1, 0, "hooks" }, + { 0, 1, 0, "info" }, + { 0, 0, 1, "info/sparse-checkout" }, + { 1, 1, 0, "logs" }, + { 1, 1, 1, "logs/HEAD" }, + { 0, 1, 0, "lost-found" }, + { 0, 1, 0, "objects" }, + { 0, 1, 0, "refs" }, + { 0, 1, 0, "remotes" }, + { 0, 1, 0, "worktrees" }, + { 0, 1, 0, "rr-cache" }, + { 0, 1, 0, "svn" }, + { 0, 0, 0, "config" }, + { 1, 0, 0, "gc.pid" }, + { 0, 0, 0, "packed-refs" }, + { 0, 0, 0, "shallow" }, + { 0, 0, 0, NULL } }; static void update_common_dir(struct strbuf *buf, int git_dir_len) { char *base = buf->buf + git_dir_len; - const char **p; + const struct common_dir *p; if (is_dir_file(base, "logs", "HEAD") || is_dir_file(base, "info", "sparse-checkout")) return; /* keep this in $GIT_DIR */ - for (p = common_list; *p; p++) { - const char *path = *p; - int is_dir = 0; - if (*path == '!') - path++; - if (*path == '/') { - path++; - is_dir = 1; - } - if (is_dir && dir_prefix(base, path)) { + for (p = common_list; p->dirname; p++) { + const char *path = p->dirname; + if (p->is_dir && dir_prefix(base, path)) { replace_dir(buf, git_dir_len, get_git_common_dir()); return; } - if (!is_dir && !strcmp(base, path)) { + if (!p->is_dir && !strcmp(base, path)) { replace_dir(buf, git_dir_len, get_git_common_dir()); return; } @@ -129,16 +145,16 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len) void report_linked_checkout_garbage(void) { struct strbuf sb = STRBUF_INIT; - const char **p; + const struct common_dir *p; int len; if (!git_common_dir_env) return; strbuf_addf(&sb, "%s/", get_git_dir()); len = sb.len; - for (p = common_list; *p; p++) { - const char *path = *p; - if (*path == '!') + for (p = common_list; p->dirname; p++) { + const char *path = p->dirname; + if (p->ignore_garbage) continue; strbuf_setlen(&sb, len); strbuf_addstr(&sb, path); From 4e09cf2acf00c63848c365479c61d80af62eceba Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 31 Aug 2015 22:13:10 -0400 Subject: [PATCH 046/539] path: optimize common dir checking Instead of a linear search over common_list to check whether a path is common, use a trie. The trie search operates on path prefixes, and handles excludes. Signed-off-by: David Turner Signed-off-by: Junio C Hamano --- path.c | 227 +++++++++++++++++++++++++++++++++++++++--- t/t0060-path-utils.sh | 1 + 2 files changed, 214 insertions(+), 14 deletions(-) diff --git a/path.c b/path.c index 2215ab5bf781b0..7e959c9cddd583 100644 --- a/path.c +++ b/path.c @@ -121,25 +121,224 @@ static struct common_dir common_list[] = { { 0, 0, 0, NULL } }; -static void update_common_dir(struct strbuf *buf, int git_dir_len) +/* + * A compressed trie. A trie node consists of zero or more characters that + * are common to all elements with this prefix, optionally followed by some + * children. If value is not NULL, the trie node is a terminal node. + * + * For example, consider the following set of strings: + * abc + * def + * definite + * definition + * + * The trie would look look like: + * root: len = 0, children a and d non-NULL, value = NULL. + * a: len = 2, contents = bc, value = (data for "abc") + * d: len = 2, contents = ef, children i non-NULL, value = (data for "def") + * i: len = 3, contents = nit, children e and i non-NULL, value = NULL + * e: len = 0, children all NULL, value = (data for "definite") + * i: len = 2, contents = on, children all NULL, + * value = (data for "definition") + */ +struct trie { + struct trie *children[256]; + int len; + char *contents; + void *value; +}; + +static struct trie *make_trie_node(const char *key, void *value) { - char *base = buf->buf + git_dir_len; - const struct common_dir *p; + struct trie *new_node = xcalloc(1, sizeof(*new_node)); + new_node->len = strlen(key); + if (new_node->len) { + new_node->contents = xmalloc(new_node->len); + memcpy(new_node->contents, key, new_node->len); + } + new_node->value = value; + return new_node; +} - if (is_dir_file(base, "logs", "HEAD") || - is_dir_file(base, "info", "sparse-checkout")) - return; /* keep this in $GIT_DIR */ - for (p = common_list; p->dirname; p++) { - const char *path = p->dirname; - if (p->is_dir && dir_prefix(base, path)) { - replace_dir(buf, git_dir_len, get_git_common_dir()); - return; +/* + * Add a key/value pair to a trie. The key is assumed to be \0-terminated. + * If there was an existing value for this key, return it. + */ +static void *add_to_trie(struct trie *root, const char *key, void *value) +{ + struct trie *child; + void *old; + int i; + + if (!*key) { + /* we have reached the end of the key */ + old = root->value; + root->value = value; + return old; + } + + for (i = 0; i < root->len; i++) { + if (root->contents[i] == key[i]) + continue; + + /* + * Split this node: child will contain this node's + * existing children. + */ + child = malloc(sizeof(*child)); + memcpy(child->children, root->children, sizeof(root->children)); + + child->len = root->len - i - 1; + if (child->len) { + child->contents = xstrndup(root->contents + i + 1, + child->len); } - if (!p->is_dir && !strcmp(base, path)) { - replace_dir(buf, git_dir_len, get_git_common_dir()); - return; + child->value = root->value; + root->value = NULL; + root->len = i; + + memset(root->children, 0, sizeof(root->children)); + root->children[(unsigned char)root->contents[i]] = child; + + /* This is the newly-added child. */ + root->children[(unsigned char)key[i]] = + make_trie_node(key + i + 1, value); + return NULL; + } + + /* We have matched the entire compressed section */ + if (key[i]) { + child = root->children[(unsigned char)key[root->len]]; + if (child) { + return add_to_trie(child, key + root->len + 1, value); + } else { + child = make_trie_node(key + root->len + 1, value); + root->children[(unsigned char)key[root->len]] = child; + return NULL; } } + + old = root->value; + root->value = value; + return old; +} + +typedef int (*match_fn)(const char *unmatched, void *data, void *baton); + +/* + * Search a trie for some key. Find the longest /-or-\0-terminated + * prefix of the key for which the trie contains a value. Call fn + * with the unmatched portion of the key and the found value, and + * return its return value. If there is no such prefix, return -1. + * + * The key is partially normalized: consecutive slashes are skipped. + * + * For example, consider the trie containing only [refs, + * refs/worktree] (both with values). + * + * | key | unmatched | val from node | return value | + * |-----------------|------------|---------------|--------------| + * | a | not called | n/a | -1 | + * | refs | \0 | refs | as per fn | + * | refs/ | / | refs | as per fn | + * | refs/w | /w | refs | as per fn | + * | refs/worktree | \0 | refs/worktree | as per fn | + * | refs/worktree/ | / | refs/worktree | as per fn | + * | refs/worktree/a | /a | refs/worktree | as per fn | + * |-----------------|------------|---------------|--------------| + * + */ +static int trie_find(struct trie *root, const char *key, match_fn fn, + void *baton) +{ + int i; + int result; + struct trie *child; + + if (!*key) { + /* we have reached the end of the key */ + if (root->value && !root->len) + return fn(key, root->value, baton); + else + return -1; + } + + for (i = 0; i < root->len; i++) { + /* Partial path normalization: skip consecutive slashes. */ + if (key[i] == '/' && key[i+1] == '/') { + key++; + continue; + } + if (root->contents[i] != key[i]) + return -1; + } + + /* Matched the entire compressed section */ + key += i; + if (!*key) + /* End of key */ + return fn(key, root->value, baton); + + /* Partial path normalization: skip consecutive slashes */ + while (key[0] == '/' && key[1] == '/') + key++; + + child = root->children[(unsigned char)*key]; + if (child) + result = trie_find(child, key + 1, fn, baton); + else + result = -1; + + if (result >= 0 || (*key != '/' && *key != 0)) + return result; + if (root->value) + return fn(key, root->value, baton); + else + return -1; +} + +static struct trie common_trie; +static int common_trie_done_setup; + +static void init_common_trie(void) +{ + struct common_dir *p; + + if (common_trie_done_setup) + return; + + for (p = common_list; p->dirname; p++) + add_to_trie(&common_trie, p->dirname, p); + + common_trie_done_setup = 1; +} + +/* + * Helper function for update_common_dir: returns 1 if the dir + * prefix is common. + */ +static int check_common(const char *unmatched, void *value, void *baton) +{ + struct common_dir *dir = value; + + if (!dir) + return 0; + + if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/')) + return !dir->exclude; + + if (!dir->is_dir && unmatched[0] == 0) + return !dir->exclude; + + return 0; +} + +static void update_common_dir(struct strbuf *buf, int git_dir_len) +{ + char *base = buf->buf + git_dir_len; + init_common_trie(); + if (trie_find(&common_trie, base, check_common, NULL) > 0) + replace_dir(buf, git_dir_len, get_git_common_dir()); } void report_linked_checkout_garbage(void) diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 93605f42f27cef..1ca49e102dc258 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -271,6 +271,7 @@ test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts test_git_path GIT_COMMON_DIR=bar info/sparse-checkout .git/info/sparse-checkout +test_git_path GIT_COMMON_DIR=bar info//sparse-checkout .git/info//sparse-checkout test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master From ce414b33ec0385a2aa701b6eaa712695ac13717a Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 31 Aug 2015 22:13:11 -0400 Subject: [PATCH 047/539] refs: make refs/bisect/* per-worktree We need the place we stick refs for bisects in progress to not be shared between worktrees. So we make the refs/bisect/ hierarchy per-worktree. The is_per_worktree_ref function and associated docs learn that refs/bisect/ is per-worktree, as does the git_path code in path.c The ref-packing functions learn that per-worktree refs should not be packed (since packed-refs is common rather than per-worktree). Since refs/bisect is per-worktree, logs/refs/bisect should be too. Signed-off-by: David Turner Signed-off-by: Junio C Hamano --- Documentation/glossary-content.txt | 5 +++-- path.c | 2 ++ refs.c | 32 +++++++++++++++++++++++++++++- t/t0060-path-utils.sh | 5 +++++ t/t1400-update-ref.sh | 19 ++++++++++++++++++ t/t3210-pack-refs.sh | 7 +++++++ 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 8c6478b2f2cab1..e225974253833c 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -413,8 +413,9 @@ exclude;; [[def_per_worktree_ref]]per-worktree ref:: Refs that are per-<>, rather than - global. This is presently only <>, but might - later include other unusual refs. + global. This is presently only <> and any refs + that start with `refs/bisect/`, but might later include other + unusual refs. [[def_pseudoref]]pseudoref:: Pseudorefs are a class of files under `$GIT_DIR` which behave diff --git a/path.c b/path.c index 7e959c9cddd583..65beb2dca33359 100644 --- a/path.c +++ b/path.c @@ -107,9 +107,11 @@ static struct common_dir common_list[] = { { 0, 0, 1, "info/sparse-checkout" }, { 1, 1, 0, "logs" }, { 1, 1, 1, "logs/HEAD" }, + { 0, 1, 1, "logs/refs/bisect" }, { 0, 1, 0, "lost-found" }, { 0, 1, 0, "objects" }, { 0, 1, 0, "refs" }, + { 0, 1, 1, "refs/bisect" }, { 0, 1, 0, "remotes" }, { 0, 1, 0, "worktrees" }, { 0, 1, 0, "rr-cache" }, diff --git a/refs.c b/refs.c index 4e15f60d98ea8a..24401f71f8a8dd 100644 --- a/refs.c +++ b/refs.c @@ -304,6 +304,11 @@ struct ref_entry { }; static void read_loose_refs(const char *dirname, struct ref_dir *dir); +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len); +static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, + const char *dirname, size_t len, + int incomplete); +static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry); static struct ref_dir *get_ref_dir(struct ref_entry *entry) { @@ -312,6 +317,24 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry) dir = &entry->u.subdir; if (entry->flag & REF_INCOMPLETE) { read_loose_refs(entry->name, dir); + + /* + * Manually add refs/bisect, which, being + * per-worktree, might not appear in the directory + * listing for refs/ in the main repo. + */ + if (!strcmp(entry->name, "refs/")) { + int pos = search_ref_dir(dir, "refs/bisect/", 12); + if (pos < 0) { + struct ref_entry *child_entry; + child_entry = create_dir_entry(dir->ref_cache, + "refs/bisect/", + 12, 1); + add_entry_to_dir(dir, child_entry); + read_loose_refs("refs/bisect", + &child_entry->u.subdir); + } + } entry->flag &= ~REF_INCOMPLETE; } return dir; @@ -2649,6 +2672,8 @@ struct pack_refs_cb_data { struct ref_to_prune *ref_to_prune; }; +static int is_per_worktree_ref(const char *refname); + /* * An each_ref_entry_fn that is run over loose references only. If * the loose reference can be packed, add an entry in the packed ref @@ -2662,6 +2687,10 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data) struct ref_entry *packed_entry; int is_tag_ref = starts_with(entry->name, "refs/tags/"); + /* Do not pack per-worktree refs: */ + if (is_per_worktree_ref(entry->name)) + return 0; + /* ALWAYS pack tags */ if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref) return 0; @@ -2856,7 +2885,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) static int is_per_worktree_ref(const char *refname) { - return !strcmp(refname, "HEAD"); + return !strcmp(refname, "HEAD") || + starts_with(refname, "refs/bisect/"); } static int is_pseudoref_syntax(const char *refname) diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 1ca49e102dc258..627ef854d5c804 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -266,6 +266,10 @@ test_expect_success 'setup common repository' 'git --git-dir=bar init' test_git_path GIT_COMMON_DIR=bar index .git/index test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD +test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo .git/logs/refs/bisect/foo +test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo bar/logs/refs/bisec/foo +test_git_path GIT_COMMON_DIR=bar logs/refs/bisec bar/logs/refs/bisec +test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo bar/logs/refs/bisectfoo test_git_path GIT_COMMON_DIR=bar objects bar/objects test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude @@ -276,6 +280,7 @@ test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master +test_git_path GIT_COMMON_DIR=bar refs/bisect/foo .git/refs/bisect/foo test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me test_git_path GIT_COMMON_DIR=bar config bar/config test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 97406fa4b137af..af1b20dd5c6763 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -1130,4 +1130,23 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches ) ' +test_expect_success 'handle per-worktree refs in refs/bisect' ' + git commit --allow-empty -m "initial commit" && + git worktree add -b branch worktree && + ( + cd worktree && + git commit --allow-empty -m "test commit" && + git for-each-ref >for-each-ref.out && + ! grep refs/bisect for-each-ref.out && + git update-ref refs/bisect/something HEAD && + git rev-parse refs/bisect/something >../worktree-head && + git for-each-ref | grep refs/bisect/something + ) && + test_path_is_missing .git/refs/bisect && + test_must_fail git rev-parse refs/bisect/something && + git update-ref refs/bisect/something HEAD && + git rev-parse refs/bisect/something >main-head && + ! test_cmp main-head worktree-head +' + test_done diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 7b5b6d452e3762..db244d2f8820cf 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -160,6 +160,13 @@ test_expect_success 'pack ref directly below refs/' ' test_path_is_missing .git/refs/top ' +test_expect_success 'do not pack ref in refs/bisect' ' + git update-ref refs/bisect/local HEAD && + git pack-refs --all --prune && + ! grep refs/bisect/local .git/packed-refs >/dev/null && + test_path_is_file .git/refs/bisect/local +' + test_expect_success 'disable reflogs' ' git config core.logallrefupdates false && rm -rf .git/logs From ff60ffdc05fe8e2d26c488ab18c66b7fa746fcc6 Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Mon, 31 Aug 2015 14:06:38 +0200 Subject: [PATCH 048/539] git-quiltimport: add commandline option --series The quilt series file doesn't have to be located in the same directory with the patches and can be named differently than 'series' as well. This patch adds a commandline option to allow for a non-standard series filename and location. Signed-off-by: Juerg Haefliger Signed-off-by: Junio C Hamano --- Documentation/git-quiltimport.txt | 11 +++++++++-- git-quiltimport.sh | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index d64388cb8e454b..ff633b0db7d54d 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] 'git quiltimport' [--dry-run | -n] [--author ] [--patches ] + [--series ] DESCRIPTION @@ -42,13 +43,19 @@ OPTIONS information can be found in the patch description. --patches :: - The directory to find the quilt patches and the - quilt series file. + The directory to find the quilt patches. + The default for the patch directory is patches or the value of the $QUILT_PATCHES environment variable. +--series :: + The quilt series file. ++ +The default for the series file is /series +or the value of the $QUILT_SERIES environment +variable. + GIT --- Part of the linkgit:git[1] suite diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 167d79fea809b9..6d3a88decdeee3 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -6,7 +6,8 @@ git quiltimport [options] -- n,dry-run dry run author= author name and email address for patches without any -patches= path to the quilt series and patches +patches= path to the quilt patches +series= path to the quilt series file " SUBDIRECTORY_ON=Yes . git-sh-setup @@ -27,6 +28,10 @@ do shift QUILT_PATCHES="$1" ;; + --series) + shift + QUILT_SERIES="$1" + ;; --) shift break;; @@ -53,6 +58,13 @@ if ! [ -d "$QUILT_PATCHES" ] ; then exit 1 fi +# Quilt series file +: ${QUILT_SERIES:=$QUILT_PATCHES/series} +if ! [ -e "$QUILT_SERIES" ] ; then + echo "The \"$QUILT_SERIES\" file does not exist." + exit 1 +fi + # Temporary directories tmp_dir="$GIT_DIR"/rebase-apply tmp_msg="$tmp_dir/msg" @@ -135,5 +147,5 @@ do commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) && git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4 fi -done 3<"$QUILT_PATCHES/series" +done 3<"$QUILT_SERIES" rm -rf $tmp_dir || exit 5 From 661a8cf408e83e4901bf09e2a48e9306622442dd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 1 Sep 2015 16:22:43 -0400 Subject: [PATCH 049/539] run-command: provide in_async query function It's not easy for arbitrary code to find out whether it is running in an async process or not. A top-level function which is fed to start_async() can know (you just pass down an argument saying "you are async"). But that function may call other global functions, and we would not want to have to pass the information all the way through the call stack. Nor can we simply set a global variable, as those may be shared between async threads and the main thread (if the platform supports pthreads). We need pthread tricks _or_ a global variable, depending on how start_async is implemented. The callers don't have enough information to do this right, so let's provide a simple query function that does. Fortunately we can reuse the existing infrastructure to make the pthread case simple (and even simplify die_async() by using our new function). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- run-command.c | 16 +++++++++++++++- run-command.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/run-command.c b/run-command.c index 4d73e90fad1591..6131a44bddabd4 100644 --- a/run-command.c +++ b/run-command.c @@ -608,7 +608,7 @@ static NORETURN void die_async(const char *err, va_list params) { vreportf("fatal: ", err, params); - if (!pthread_equal(main_thread, pthread_self())) { + if (in_async()) { struct async *async = pthread_getspecific(async_key); if (async->proc_in >= 0) close(async->proc_in); @@ -627,6 +627,13 @@ static int async_die_is_recursing(void) return ret != NULL; } +int in_async(void) +{ + if (!main_thread_set) + return 0; /* no asyncs started yet */ + return !pthread_equal(main_thread, pthread_self()); +} + #else static struct { @@ -666,6 +673,12 @@ int git_atexit(void (*handler)(void)) } #define atexit git_atexit +static int process_is_async; +int in_async(void) +{ + return process_is_async; +} + #endif int start_async(struct async *async) @@ -725,6 +738,7 @@ int start_async(struct async *async) if (need_out) close(fdout[0]); git_atexit_clear(); + process_is_async = 1; exit(!!async->proc(proc_in, proc_out, async->data)); } diff --git a/run-command.h b/run-command.h index 1103805af1b01e..4aaac7ce0f3a46 100644 --- a/run-command.h +++ b/run-command.h @@ -113,5 +113,6 @@ struct async { int start_async(struct async *async); int finish_async(struct async *async); +int in_async(void); #endif From fd89433dd03babb6c8d760092c3e499f6a4145f4 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 1 Sep 2015 16:24:13 -0400 Subject: [PATCH 050/539] pkt-line: show packets in async processes as "sideband" If you run "GIT_TRACE_PACKET=1 git push", you may get confusing output like (line prefixes omitted for clarity): packet: push< \1000eunpack ok0019ok refs/heads/master0000 packet: push< unpack ok packet: push< ok refs/heads/master packet: push< 0000 packet: push< 0000 Why do we see the data twice, once apparently wrapped inside another pkt-line, and once unwrapped? Why do we get two flush packets? The answer is that we start an async process to demux the sideband data. The first entry comes from the sideband process reading the data, and the second from push itself. Likewise, the first flush is inside the demuxed packet, and the second is an actual sideband flush. We can make this a bit more clear by marking the sideband demuxer explicitly as "sideband" rather than "push". The most elegant way to do this would be to simply call packet_trace_identity() inside the sideband demuxer. But we can't do that reliably, because it relies on a global variable, which might be shared if pthreads are in use. What we really need is thread-local storage for packet_trace_identity. But the async code does not provide an interface for that, and it would be messy to add it here (we'd have to care about pthreads, initializing our pthread_key_t ahead of time, etc). So instead, let us just assume that any async process is handling sideband data. That's always true now, and is likely to remain so in the future. The output looks like: packet: sideband< \1000eunpack ok0019ok refs/heads/master0000 packet: push< unpack ok packet: push< ok refs/heads/master packet: push< 0000 packet: sideband< 0000 Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- pkt-line.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkt-line.c b/pkt-line.c index 08a1427c0d4f67..62fdb37079fa31 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -1,5 +1,6 @@ #include "cache.h" #include "pkt-line.h" +#include "run-command.h" char packet_buffer[LARGE_PACKET_MAX]; static const char *packet_trace_prefix = "git"; @@ -11,6 +12,11 @@ void packet_trace_identity(const char *prog) packet_trace_prefix = xstrdup(prog); } +static const char *get_trace_prefix(void) +{ + return in_async() ? "sideband" : packet_trace_prefix; +} + static int packet_trace_pack(const char *buf, unsigned int len, int sideband) { if (!sideband) { @@ -57,7 +63,7 @@ static void packet_trace(const char *buf, unsigned int len, int write) strbuf_init(&out, len+32); strbuf_addf(&out, "packet: %12s%c ", - packet_trace_prefix, write ? '>' : '<'); + get_trace_prefix(), write ? '>' : '<'); /* XXX we should really handle printable utf8 */ for (i = 0; i < len; i++) { From a9e38359e356de7d6397395bdde8af61440262d0 Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Thu, 3 Sep 2015 11:14:07 +0200 Subject: [PATCH 051/539] git-p4: add config git-p4.pathEncoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perforce keeps the encoding of a path as given by the originating OS. Git expects paths encoded as UTF-8. Add a config to tell git-p4 what encoding Perforce had used for the paths. This encoding is used to transcode the paths to UTF-8. As an example, Perforce on Windows often uses “cp1252” to encode path names. Signed-off-by: Lars Schneider Acked-by: Luke Diamand Signed-off-by: Lars Schneider Signed-off-by: Junio C Hamano --- Documentation/git-p4.txt | 7 ++++ git-p4.py | 11 +++++++ t/t9822-git-p4-path-encoding.sh | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100755 t/t9822-git-p4-path-encoding.sh diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 82aa5d60736ccc..12a57d49f4d6b4 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -510,6 +510,13 @@ git-p4.useClientSpec:: option '--use-client-spec'. See the "CLIENT SPEC" section above. This variable is a boolean, not the name of a p4 client. +git-p4.pathEncoding:: + Perforce keeps the encoding of a path as given by the originating OS. + Git expects paths encoded as UTF-8. Use this config to tell git-p4 + what encoding Perforce had used for the paths. This encoding is used + to transcode the paths to UTF-8. As an example, Perforce on Windows + often uses “cp1252” to encode path names. + Submit variables ~~~~~~~~~~~~~~~~ git-p4.detectRenames:: diff --git a/git-p4.py b/git-p4.py index 073f87bbfdc1dc..b1ad86de7f2b73 100755 --- a/git-p4.py +++ b/git-p4.py @@ -2213,6 +2213,17 @@ def streamOneP4File(self, file, contents): text = regexp.sub(r'$\1$', text) contents = [ text ] + if gitConfig("git-p4.pathEncoding"): + relPath = relPath.decode(gitConfig("git-p4.pathEncoding")).encode('utf8', 'replace') + elif self.verbose: + try: + relPath.decode('ascii') + except: + print ( + "Path with Non-ASCII characters detected and no path encoding defined. " + "Please check the encoding: %s" % relPath + ) + self.gitStream.write("M %s inline %s\n" % (git_mode, relPath)) # total length... diff --git a/t/t9822-git-p4-path-encoding.sh b/t/t9822-git-p4-path-encoding.sh new file mode 100755 index 00000000000000..7b83e696a92a5c --- /dev/null +++ b/t/t9822-git-p4-path-encoding.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='Clone repositories with non ASCII paths' + +. ./lib-git-p4.sh + +UTF8_ESCAPED="a-\303\244_o-\303\266_u-\303\274.txt" +ISO8859_ESCAPED="a-\344_o-\366_u-\374.txt" + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create a repo containing iso8859-1 encoded paths' ' + ( + cd "$cli" && + ISO8859="$(printf "$ISO8859_ESCAPED")" && + echo content123 >"$ISO8859" && + p4 add "$ISO8859" && + p4 submit -d "test commit" + ) +' + +test_expect_failure 'Clone auto-detects depot with iso8859-1 paths' ' + git p4 clone --destination="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + UTF8="$(printf "$UTF8_ESCAPED")" && + echo "$UTF8" >expect && + git -c core.quotepath=false ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone repo containing iso8859-1 encoded paths with git-p4.pathEncoding' ' + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.pathEncoding iso8859-1 && + git p4 clone --use-client-spec --destination="$git" //depot && + UTF8="$(printf "$UTF8_ESCAPED")" && + echo "$UTF8" >expect && + git -c core.quotepath=false ls-files >actual && + test_cmp expect actual && + + echo content123 >expect && + cat "$UTF8" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done From 74703a1e4dfc5affcb8944e78b53f0817b492246 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Wed, 2 Sep 2015 14:42:24 -0700 Subject: [PATCH 052/539] submodule: rewrite `module_list` shell function in C Most of the submodule operations work on a set of submodules. Calculating and using this set is usually done via: module_list "$@" | { while read mode sha1 stage sm_path do # the actual operation done } Currently the function `module_list` is implemented in the git-submodule.sh as a shell script wrapping a perl script. The rewrite is in C, such that it is faster and can later be easily adapted when other functions are rewritten in C. git-submodule.sh, similar to the builtin commands, will navigate to the top-most directory of the repository and keep the subdirectory as a variable. As the helper is called from within the git-submodule.sh script, we are already navigated to the root level, but the path arguments are still relative to the subdirectory we were in when calling git-submodule.sh. That's why there is a `--prefix` option pointing to an alternative path which to anchor relative path arguments. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/submodule--helper.c | 128 ++++++++++++++++++++++++++++++++++++ git-submodule.sh | 54 ++------------- git.c | 1 + 6 files changed, 138 insertions(+), 48 deletions(-) create mode 100644 builtin/submodule--helper.c diff --git a/.gitignore b/.gitignore index 4fd81baf856669..1c2f8321386f89 100644 --- a/.gitignore +++ b/.gitignore @@ -155,6 +155,7 @@ /git-status /git-stripspace /git-submodule +/git-submodule--helper /git-svn /git-symbolic-ref /git-tag diff --git a/Makefile b/Makefile index 24b636dc58588d..d434e73d73231d 100644 --- a/Makefile +++ b/Makefile @@ -901,6 +901,7 @@ BUILTIN_OBJS += builtin/shortlog.o BUILTIN_OBJS += builtin/show-branch.o BUILTIN_OBJS += builtin/show-ref.o BUILTIN_OBJS += builtin/stripspace.o +BUILTIN_OBJS += builtin/submodule--helper.o BUILTIN_OBJS += builtin/symbolic-ref.o BUILTIN_OBJS += builtin/tag.o BUILTIN_OBJS += builtin/unpack-file.o diff --git a/builtin.h b/builtin.h index 839483de6ec2bb..924e6c4f8f9d47 100644 --- a/builtin.h +++ b/builtin.h @@ -119,6 +119,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); +extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tag(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c new file mode 100644 index 00000000000000..10db4e6f6a5c5e --- /dev/null +++ b/builtin/submodule--helper.c @@ -0,0 +1,128 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "quote.h" +#include "pathspec.h" +#include "dir.h" +#include "utf8.h" + +struct module_list { + const struct cache_entry **entries; + int alloc, nr; +}; +#define MODULE_LIST_INIT { NULL, 0, 0 } + +static int module_list_compute(int argc, const char **argv, + const char *prefix, + struct pathspec *pathspec, + struct module_list *list) +{ + int i, result = 0; + char *max_prefix, *ps_matched = NULL; + int max_prefix_len; + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + prefix, argv); + + /* Find common prefix for all pathspec's */ + max_prefix = common_prefix(pathspec); + max_prefix_len = max_prefix ? strlen(max_prefix) : 0; + + if (pathspec->nr) + ps_matched = xcalloc(pathspec->nr, 1); + + if (read_cache() < 0) + die(_("index file corrupt")); + + for (i = 0; i < active_nr; i++) { + const struct cache_entry *ce = active_cache[i]; + + if (!S_ISGITLINK(ce->ce_mode) || + !match_pathspec(pathspec, ce->name, ce_namelen(ce), + max_prefix_len, ps_matched, 1)) + continue; + + ALLOC_GROW(list->entries, list->nr + 1, list->alloc); + list->entries[list->nr++] = ce; + while (i + 1 < active_nr && + !strcmp(ce->name, active_cache[i + 1]->name)) + /* + * Skip entries with the same name in different stages + * to make sure an entry is returned only once. + */ + i++; + } + free(max_prefix); + + if (ps_matched && report_path_error(ps_matched, pathspec, prefix)) + result = -1; + + free(ps_matched); + + return result; +} + +static int module_list(int argc, const char **argv, const char *prefix) +{ + int i; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + + struct option module_list_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper list [--prefix=] [...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_list_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) { + printf("#unmatched\n"); + return 1; + } + + for (i = 0; i < list.nr; i++) { + const struct cache_entry *ce = list.entries[i]; + + if (ce_stage(ce)) + printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); + else + printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce)); + + utf8_fprintf(stdout, "%s\n", ce->name); + } + return 0; +} + + +struct cmd_struct { + const char *cmd; + int (*fn)(int, const char **, const char *); +}; + +static struct cmd_struct commands[] = { + {"list", module_list}, +}; + +int cmd_submodule__helper(int argc, const char **argv, const char *prefix) +{ + int i; + if (argc < 2) + die(_("fatal: submodule--helper subcommand must be " + "called with a subcommand")); + + for (i = 0; i < ARRAY_SIZE(commands); i++) + if (!strcmp(argv[1], commands[i].cmd)) + return commands[i].fn(argc - 1, argv + 1, prefix); + + die(_("fatal: '%s' is not a valid submodule--helper " + "subcommand"), argv[1]); +} diff --git a/git-submodule.sh b/git-submodule.sh index 36797c3c00f489..95c04fc60ce7a8 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -145,48 +145,6 @@ relative_path () echo "$result$target" } -# -# Get submodule info for registered submodules -# $@ = path to limit submodule list -# -module_list() -{ - eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")" - ( - git ls-files -z --error-unmatch --stage -- "$@" || - echo "unmatched pathspec exists" - ) | - @@PERL@@ -e ' - my %unmerged = (); - my ($null_sha1) = ("0" x 40); - my @out = (); - my $unmatched = 0; - $/ = "\0"; - while () { - 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}++) { - push @out, "$mode $null_sha1 U\t$path\n"; - } - next; - } - push @out, "$_\n"; - } - if ($unmatched) { - print "#unmatched\n"; - } else { - print for (@out); - } - ' -} - die_if_unmatched () { if test "$1" = "#unmatched" @@ -532,7 +490,7 @@ cmd_foreach() # command in the subshell (and a recursive call to this function) exec 3<&0 - module_list | + git submodule--helper list --prefix "$wt_prefix"| while read mode sha1 stage sm_path do die_if_unmatched "$mode" @@ -592,7 +550,7 @@ cmd_init() shift done - module_list "$@" | + git submodule--helper list --prefix "$wt_prefix" "$@" | while read mode sha1 stage sm_path do die_if_unmatched "$mode" @@ -674,7 +632,7 @@ cmd_deinit() die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")" fi - module_list "$@" | + git submodule--helper list --prefix "$wt_prefix" "$@" | while read mode sha1 stage sm_path do die_if_unmatched "$mode" @@ -790,7 +748,7 @@ cmd_update() fi cloned_modules= - module_list "$@" | { + git submodule--helper list --prefix "$wt_prefix" "$@" | { err= while read mode sha1 stage sm_path do @@ -1222,7 +1180,7 @@ cmd_status() shift done - module_list "$@" | + git submodule--helper list --prefix "$wt_prefix" "$@" | while read mode sha1 stage sm_path do die_if_unmatched "$mode" @@ -1299,7 +1257,7 @@ cmd_sync() esac done cd_to_toplevel - module_list "$@" | + git submodule--helper list --prefix "$wt_prefix" "$@" | while read mode sha1 stage sm_path do die_if_unmatched "$mode" diff --git a/git.c b/git.c index 55c327c7b3d2cd..deecba032fc516 100644 --- a/git.c +++ b/git.c @@ -469,6 +469,7 @@ static struct cmd_struct commands[] = { { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, + { "submodule--helper", cmd_submodule__helper, RUN_SETUP }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tag", cmd_tag, RUN_SETUP }, { "unpack-file", cmd_unpack_file, RUN_SETUP }, From 0ea306ef1701d6f42e74d3c33addfcd630248904 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Wed, 2 Sep 2015 14:42:25 -0700 Subject: [PATCH 053/539] submodule: rewrite `module_name` shell function in C This implements the helper `name` in C instead of shell, yielding a nice performance boost. Before this patch, I measured a time (best out of three): $ time ./t7400-submodule-basic.sh >/dev/null real 0m11.066s user 0m3.348s sys 0m8.534s With this patch applied I measured (also best out of three) $ time ./t7400-submodule-basic.sh >/dev/null real 0m10.063s user 0m3.044s sys 0m7.487s Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/submodule--helper.c | 22 ++++++++++++++++++++++ git-submodule.sh | 32 +++++++------------------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 10db4e6f6a5c5e..bc79c41b587086 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -5,6 +5,9 @@ #include "pathspec.h" #include "dir.h" #include "utf8.h" +#include "submodule.h" +#include "submodule-config.h" +#include "string-list.h" struct module_list { const struct cache_entry **entries; @@ -102,6 +105,24 @@ static int module_list(int argc, const char **argv, const char *prefix) return 0; } +static int module_name(int argc, const char **argv, const char *prefix) +{ + const struct submodule *sub; + + if (argc != 2) + usage(_("git submodule--helper name ")); + + gitmodules_config(); + sub = submodule_from_path(null_sha1, argv[1]); + + if (!sub) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + argv[1]); + + printf("%s\n", sub->name); + + return 0; +} struct cmd_struct { const char *cmd; @@ -110,6 +131,7 @@ struct cmd_struct { static struct cmd_struct commands[] = { {"list", module_list}, + {"name", module_name}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/git-submodule.sh b/git-submodule.sh index 95c04fc60ce7a8..2be8da2396eb11 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -178,24 +178,6 @@ get_submodule_config () { printf '%s' "${value:-$default}" } - -# -# Map submodule path to submodule name -# -# $1 = path -# -module_name() -{ - # Do we have "submodule..path = $1" defined in .gitmodules file? - sm_path="$1" - re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g') - name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | - sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) - test -z "$name" && - die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")" - printf '%s\n' "$name" -} - # # Clone a submodule # @@ -498,7 +480,7 @@ cmd_foreach() then displaypath=$(relative_path "$sm_path") say "$(eval_gettext "Entering '\$prefix\$displaypath'")" - name=$(module_name "$sm_path") + name=$(git submodule--helper name "$sm_path") ( prefix="$prefix$sm_path/" clear_local_git_env @@ -554,7 +536,7 @@ cmd_init() while read mode sha1 stage sm_path do die_if_unmatched "$mode" - name=$(module_name "$sm_path") || exit + name=$(git submodule--helper name "$sm_path") || exit displaypath=$(relative_path "$sm_path") @@ -636,7 +618,7 @@ cmd_deinit() while read mode sha1 stage sm_path do die_if_unmatched "$mode" - name=$(module_name "$sm_path") || exit + name=$(git submodule--helper name "$sm_path") || exit displaypath=$(relative_path "$sm_path") @@ -758,7 +740,7 @@ cmd_update() echo >&2 "Skipping unmerged submodule $prefix$sm_path" continue fi - name=$(module_name "$sm_path") || exit + name=$(git submodule--helper name "$sm_path") || exit url=$(git config submodule."$name".url) branch=$(get_submodule_config "$name" branch master) if ! test -z "$update" @@ -1022,7 +1004,7 @@ cmd_summary() { # Respect the ignore setting for --for-status. if test -n "$for_status" then - name=$(module_name "$sm_path") + name=$(git submodule--helper name "$sm_path") ignore_config=$(get_submodule_config "$name" ignore none) test $status != A && test $ignore_config = all && continue fi @@ -1184,7 +1166,7 @@ cmd_status() while read mode sha1 stage sm_path do die_if_unmatched "$mode" - name=$(module_name "$sm_path") || exit + name=$(git submodule--helper name "$sm_path") || exit url=$(git config submodule."$name".url) displaypath=$(relative_path "$prefix$sm_path") if test "$stage" = U @@ -1261,7 +1243,7 @@ cmd_sync() while read mode sha1 stage sm_path do die_if_unmatched "$mode" - name=$(module_name "$sm_path") + name=$(git submodule--helper name "$sm_path") url=$(git config -f .gitmodules --get submodule."$name".url) # Possibly a url relative to parent From 2df4e29c85dee2fe8b89e7878a38b59062e103ef Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:51 +0100 Subject: [PATCH 054/539] Documentation/blame-options: don't list date formats This list is already incomplete (missing "raw") and we're about to add new formats. Remove it and refer to the canonical documentation in git-log(1). Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Documentation/blame-options.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index a09969ba086609..760eab7428357a 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -63,11 +63,10 @@ include::line-range-format.txt[] `-` to make the command read from the standard input). --date :: - The value is one of the following alternatives: - {relative,local,default,iso,rfc,short}. If --date is not + Specifies the format used to output dates. If --date is not provided, the value of the blame.date config variable is used. If the blame.date config variable is also not set, the - iso format is used. For more information, See the discussion + iso format is used. For supported values, see the discussion of the --date option at linkgit:git-log[1]. -M||:: From 78a844160bd1273bad89fdc0df13a1037ab4b1a4 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:52 +0100 Subject: [PATCH 055/539] Documentation/config: don't list date formats This list is already incomplete (missing "raw") and we're about to add new formats. Since this option sets a default for git-log's --date option, just refer to git-log(1). Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 3e37b93ed2ac3e..e08be76b80c584 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1781,9 +1781,7 @@ log.abbrevCommit:: log.date:: Set the default date-time mode for the 'log' command. Setting a value for log.date is similar to using 'git log''s - `--date` option. Possible values are `relative`, `local`, - `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1] - for details. + `--date` option. See linkgit:git-log[1] for details. log.decorate:: Print out the ref names of any commits that are shown by the log From 8f50d263d70702c9409eb17cce10ea5b0831223a Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:53 +0100 Subject: [PATCH 056/539] Documentation/git-for-each-ref: don't list date formats We are about to add a new set of supported date formats and do not want to have to maintain the same list in several different bits of documentation. Refer to git-rev-list(1) which contains the full list of supported formats. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 7f8d9a5b5f358b..d0626395570817 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -134,9 +134,8 @@ the object referred by the ref does not cause an error. It returns an empty string instead. As a special case for the date-type fields, you may specify a format for -the date by adding one of `:default`, `:relative`, `:short`, `:local`, -`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g. -`%(taggerdate:relative)`. +the date by adding `:` followed by date format name (see the +values the `--date` option to linkgit::git-rev-list[1] takes). EXAMPLES From 4b1c5e1d268fa1e7b4344e7d94de1edc5ead49a5 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:54 +0100 Subject: [PATCH 057/539] Documentation/rev-list: don't list date formats We are about to add several new date formats which will make this list too long to display in a single line. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Documentation/git-rev-list.txt | 2 +- Documentation/rev-list-options.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index b10ea60833ca67..51c7d072082340 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -45,7 +45,7 @@ SYNOPSIS [ --regexp-ignore-case | -i ] [ --extended-regexp | -E ] [ --fixed-strings | -F ] - [ --date=(local|relative|default|iso|iso-strict|rfc|short) ] + [ --date=] [ [ --objects | --objects-edge | --objects-edge-aggressive ] [ --unpacked ] ] [ --pretty | --header ] diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a9b808fab321e8..14c4cce60fca07 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -699,7 +699,7 @@ include::pretty-options.txt[] --relative-date:: Synonym for `--date=relative`. ---date=(relative|local|default|iso|iso-strict|rfc|short|raw):: +--date=:: Only takes effect for dates shown in human-readable format, such as when using `--pretty`. `log.date` config variable sets a default value for the log command's `--date` option. From 547ed71636dbfab2dc303ba8cabb29c2cab22f1e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 3 Sep 2015 22:48:55 +0100 Subject: [PATCH 058/539] fast-import: switch crash-report date to iso8601 When fast-import emits a crash report, it does so in the user's local timezone. But because we omit the timezone completely for DATE_LOCAL, a reader of the report does not immediately know which time zone was used. Let's switch this to ISO8601 instead, which includes the time zone. This does mean we will show the time in UTC, but that's not a big deal. A crash report like this will either be looked at immediately (in which case nobody even looks at the timestamp), or it will be passed along to a developer to debug, in which case the original timezone is less likely to be of interest. Signed-off-by: Jeff King Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- fast-import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast-import.c b/fast-import.c index 9363cc7829d1b5..1343865278ec69 100644 --- a/fast-import.c +++ b/fast-import.c @@ -421,7 +421,7 @@ static void write_crash_report(const char *err) fprintf(rpt, "fast-import crash report:\n"); fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid()); fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid()); - fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_MODE(LOCAL))); + fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601))); fputc('\n', rpt); fputs("fatal: ", rpt); From f95cecf43335dd8e80e954c2f998dc0e8b0dd070 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:56 +0100 Subject: [PATCH 059/539] t6300: introduce test_date() helper This moves the setup of the "expected" file inside the test case. The helper function has the advantage that we can use SQ in the file content without needing to escape the quotes. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 92 ++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index c7f368c77c0576..c14c4578286b74 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -146,85 +146,73 @@ test_expect_success 'Check invalid format specifiers are errors' ' test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads ' -cat >expected <<\EOF -'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200' -'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200' -EOF +test_date () { + f=$1 && + committer_date=$2 && + author_date=$3 && + tagger_date=$4 && + cat >expected <<-EOF && + 'refs/heads/master' '$committer_date' '$author_date' + 'refs/tags/testtag' '$tagger_date' + EOF + ( + git for-each-ref --shell \ + --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \ + refs/heads && + git for-each-ref --shell \ + --format="%(refname) %(taggerdate${f:+:$f})" \ + refs/tags + ) >actual && + test_cmp expected actual +} test_expect_success 'Check unformatted date fields output' ' - (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual && - test_cmp expected actual + test_date "" \ + "Mon Jul 3 17:18:43 2006 +0200" \ + "Mon Jul 3 17:18:44 2006 +0200" \ + "Mon Jul 3 17:18:45 2006 +0200" ' test_expect_success 'Check format "default" formatted date fields output' ' - f=default && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date default \ + "Mon Jul 3 17:18:43 2006 +0200" \ + "Mon Jul 3 17:18:44 2006 +0200" \ + "Mon Jul 3 17:18:45 2006 +0200" ' # Don't know how to do relative check because I can't know when this script # is going to be run and can't fake the current time to git, and hence can't # provide expected output. Instead, I'll just make sure that "relative" # doesn't exit in error -# -#cat >expected <<\EOF -# -#EOF -# test_expect_success 'Check format "relative" date fields output' ' f=relative && (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual ' -cat >expected <<\EOF -'refs/heads/master' '2006-07-03' '2006-07-03' -'refs/tags/testtag' '2006-07-03' -EOF - test_expect_success 'Check format "short" date fields output' ' - f=short && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date short 2006-07-03 2006-07-03 2006-07-03 ' -cat >expected <<\EOF -'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006' -'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006' -EOF - test_expect_success 'Check format "local" date fields output' ' - f=local && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date local \ + "Mon Jul 3 15:18:43 2006" \ + "Mon Jul 3 15:18:44 2006" \ + "Mon Jul 3 15:18:45 2006" ' -cat >expected <<\EOF -'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200' -'refs/tags/testtag' '2006-07-03 17:18:45 +0200' -EOF - test_expect_success 'Check format "iso8601" date fields output' ' - f=iso8601 && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date iso8601 \ + "2006-07-03 17:18:43 +0200" \ + "2006-07-03 17:18:44 +0200" \ + "2006-07-03 17:18:45 +0200" ' -cat >expected <<\EOF -'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200' -'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200' -EOF - test_expect_success 'Check format "rfc2822" date fields output' ' - f=rfc2822 && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date rfc2822 \ + "Mon, 3 Jul 2006 17:18:43 +0200" \ + "Mon, 3 Jul 2006 17:18:44 +0200" \ + "Mon, 3 Jul 2006 17:18:45 +0200" ' test_expect_success 'Check format of strftime date fields' ' From f3c1ba502628cf0b6e8674f07c3850b21f365965 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:57 +0100 Subject: [PATCH 060/539] t6300: add test for "raw" date format Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index c14c4578286b74..c9b6e6d472e98b 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -215,6 +215,10 @@ test_expect_success 'Check format "rfc2822" date fields output' ' "Mon, 3 Jul 2006 17:18:45 +0200" ' +test_expect_success 'Check format "raw" date fields output' ' + test_date raw "1151939923 +0200" "1151939924 +0200" "1151939925 +0200" +' + test_expect_success 'Check format of strftime date fields' ' echo "my date is 2006-07-03" >expected && git for-each-ref \ From dc6d782c5d2526b251061daffc3e74d15c8c7095 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:48:58 +0100 Subject: [PATCH 061/539] date: check for "local" before anything else In a following commit we will make "local" orthogonal to the format. Although this will not apply to "relative", which does not use the timezone, it applies to all other formats so move the timezone conversion to the start of the function. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- date.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/date.c b/date.c index 8f9156909b8d9d..9f0a5ddf6cfe77 100644 --- a/date.c +++ b/date.c @@ -174,6 +174,9 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode) struct tm *tm; static struct strbuf timebuf = STRBUF_INIT; + if (mode->type == DATE_LOCAL) + tz = local_tzoffset(time); + if (mode->type == DATE_RAW) { strbuf_reset(&timebuf); strbuf_addf(&timebuf, "%lu %+05d", time, tz); @@ -189,9 +192,6 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode) return timebuf.buf; } - if (mode->type == DATE_LOCAL) - tz = local_tzoffset(time); - tm = time_to_tm(time, tz); if (!tm) { tm = time_to_tm(0, 0); From add00ba2de971e0c5ba00f1f02b73c5534079d2c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 3 Sep 2015 22:48:59 +0100 Subject: [PATCH 062/539] date: make "local" orthogonal to date format Most of our "--date" modes are about the format of the date: which items we show and in what order. But "--date=local" is a bit of an oddball. It means "show the date in the normal format, but using the local timezone". The timezone we use is orthogonal to the actual format, and there is no reason we could not have "localized iso8601", etc. This patch adds a "local" boolean field to "struct date_mode", and drops the DATE_LOCAL element from the date_mode_type enum (it's now just DATE_NORMAL plus local=1). The new feature is accessible to users by adding "-local" to any date mode (e.g., "iso-local"), and we retain "local" as an alias for "default-local" for backwards compatibility. Signed-off-by: Jeff King Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Documentation/rev-list-options.txt | 21 ++++++--- builtin/blame.c | 1 - cache.h | 2 +- date.c | 70 +++++++++++++++++++----------- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 14c4cce60fca07..359587c505a184 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -702,12 +702,16 @@ include::pretty-options.txt[] --date=:: Only takes effect for dates shown in human-readable format, such as when using `--pretty`. `log.date` config variable sets a default - value for the log command's `--date` option. + value for the log command's `--date` option. By default, dates + are shown in the original time zone (either committer's or + author's). If `-local` is appended to the format (e.g., + `iso-local`), the user's local time zone is used instead. + `--date=relative` shows dates relative to the current time, -e.g. ``2 hours ago''. +e.g. ``2 hours ago''. The `-local` option cannot be used with +`--raw` or `--relative`. + -`--date=local` shows timestamps in user's local time zone. +`--date=local` is an alias for `--date=default-local`. + `--date=iso` (or `--date=iso8601`) shows timestamps in a ISO 8601-like format. The differences to the strict ISO 8601 format are: @@ -730,10 +734,15 @@ format, often found in email messages. `--date=format:...` feeds the format `...` to your system `strftime`. Use `--date=format:%c` to show the date in your system locale's preferred format. See the `strftime` manual for a complete list of -format placeholders. +format placeholders. When using `-local`, the correct syntax is +`--date=format-local:...`. + -`--date=default` shows timestamps in the original time zone -(either committer's or author's). +`--date=default` is the default format, and is similar to +`--date=rfc2822`, with a few exceptions: + + - there is no comma after the day-of-week + + - the time zone is omitted when the local time zone is used ifdef::git-rev-list[] --header:: diff --git a/builtin/blame.c b/builtin/blame.c index 7cc499dd347e96..cb4ab204a9ecfc 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2600,7 +2600,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix) fewer display columns. */ blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */ break; - case DATE_LOCAL: case DATE_NORMAL: blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); break; diff --git a/cache.h b/cache.h index 04c1907d52583e..ebb72568c2fdf0 100644 --- a/cache.h +++ b/cache.h @@ -1110,7 +1110,6 @@ struct date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT, - DATE_LOCAL, DATE_ISO8601, DATE_ISO8601_STRICT, DATE_RFC2822, @@ -1118,6 +1117,7 @@ struct date_mode { DATE_RAW } type; const char *strftime_fmt; + int local; }; /* diff --git a/date.c b/date.c index 9f0a5ddf6cfe77..7c9f76998ac7a0 100644 --- a/date.c +++ b/date.c @@ -166,6 +166,7 @@ struct date_mode *date_mode_from_type(enum date_mode_type type) if (type == DATE_STRFTIME) die("BUG: cannot create anonymous strftime date_mode struct"); mode.type = type; + mode.local = 0; return &mode; } @@ -174,7 +175,7 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode) struct tm *tm; static struct strbuf timebuf = STRBUF_INIT; - if (mode->type == DATE_LOCAL) + if (mode->local) tz = local_tzoffset(time); if (mode->type == DATE_RAW) { @@ -232,7 +233,7 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode) tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900, - (mode->type == DATE_LOCAL) ? 0 : ' ', + mode->local ? 0 : ' ', tz); return timebuf.buf; } @@ -770,31 +771,50 @@ int parse_date(const char *date, struct strbuf *result) return 0; } +static enum date_mode_type parse_date_type(const char *format, const char **end) +{ + if (skip_prefix(format, "relative", end)) + return DATE_RELATIVE; + if (skip_prefix(format, "iso8601-strict", end) || + skip_prefix(format, "iso-strict", end)) + return DATE_ISO8601_STRICT; + if (skip_prefix(format, "iso8601", end) || + skip_prefix(format, "iso", end)) + return DATE_ISO8601; + if (skip_prefix(format, "rfc2822", end) || + skip_prefix(format, "rfc", end)) + return DATE_RFC2822; + if (skip_prefix(format, "short", end)) + return DATE_SHORT; + if (skip_prefix(format, "default", end)) + return DATE_NORMAL; + if (skip_prefix(format, "raw", end)) + return DATE_RAW; + if (skip_prefix(format, "format", end)) + return DATE_STRFTIME; + + die("unknown date format %s", format); +} + void parse_date_format(const char *format, struct date_mode *mode) { - if (!strcmp(format, "relative")) - mode->type = DATE_RELATIVE; - else if (!strcmp(format, "iso8601") || - !strcmp(format, "iso")) - mode->type = DATE_ISO8601; - else if (!strcmp(format, "iso8601-strict") || - !strcmp(format, "iso-strict")) - mode->type = DATE_ISO8601_STRICT; - else if (!strcmp(format, "rfc2822") || - !strcmp(format, "rfc")) - mode->type = DATE_RFC2822; - else if (!strcmp(format, "short")) - mode->type = DATE_SHORT; - else if (!strcmp(format, "local")) - mode->type = DATE_LOCAL; - else if (!strcmp(format, "default")) - mode->type = DATE_NORMAL; - else if (!strcmp(format, "raw")) - mode->type = DATE_RAW; - else if (skip_prefix(format, "format:", &format)) { - mode->type = DATE_STRFTIME; - mode->strftime_fmt = xstrdup(format); - } else + const char *p; + + /* historical alias */ + if (!strcmp(format, "local")) + format = "default-local"; + + mode->type = parse_date_type(format, &p); + mode->local = 0; + + if (skip_prefix(p, "-local", &p)) + mode->local = 1; + + if (mode->type == DATE_STRFTIME) { + if (!skip_prefix(p, ":", &p)) + die("date format missing colon separator: %s", format); + mode->strftime_fmt = xstrdup(p); + } else if (*p) die("unknown date format %s", format); } From db7bae25ed8e80a7afad9a6a9240819528c5192c Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:49:00 +0100 Subject: [PATCH 063/539] t6300: make UTC and local dates different By setting the UTC time to 23:18:43 the date in +0200 is the following day, 2006-07-04. This will ensure that the test for "short-local" to be added in the following patch tests for different output from the "short" format. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 70 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index c9b6e6d472e98b..4867df4796b6d7 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -8,8 +8,8 @@ test_description='for-each-ref test' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh -# Mon Jul 3 15:18:43 2006 +0000 -datestamp=1151939923 +# Mon Jul 3 23:18:43 2006 +0000 +datestamp=1151968723 setdate_and_increment () { GIT_COMMITTER_DATE="$datestamp +0200" datestamp=$(expr "$datestamp" + 1) @@ -61,21 +61,21 @@ test_atom head object '' test_atom head type '' test_atom head '*objectname' '' test_atom head '*objecttype' '' -test_atom head author 'A U Thor 1151939924 +0200' +test_atom head author 'A U Thor 1151968724 +0200' test_atom head authorname 'A U Thor' test_atom head authoremail '' -test_atom head authordate 'Mon Jul 3 17:18:44 2006 +0200' -test_atom head committer 'C O Mitter 1151939923 +0200' +test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200' +test_atom head committer 'C O Mitter 1151968723 +0200' test_atom head committername 'C O Mitter' test_atom head committeremail '' -test_atom head committerdate 'Mon Jul 3 17:18:43 2006 +0200' +test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200' test_atom head tag '' test_atom head tagger '' test_atom head taggername '' test_atom head taggeremail '' test_atom head taggerdate '' -test_atom head creator 'C O Mitter 1151939923 +0200' -test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200' +test_atom head creator 'C O Mitter 1151968723 +0200' +test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200' test_atom head subject 'Initial' test_atom head contents:subject 'Initial' test_atom head body '' @@ -96,7 +96,7 @@ test_atom tag parent '' test_atom tag numparent '' test_atom tag object $(git rev-parse refs/tags/testtag^0) test_atom tag type 'commit' -test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581' +test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463' test_atom tag '*objecttype' 'commit' test_atom tag author '' test_atom tag authorname '' @@ -107,18 +107,18 @@ test_atom tag committername '' test_atom tag committeremail '' test_atom tag committerdate '' test_atom tag tag 'testtag' -test_atom tag tagger 'C O Mitter 1151939925 +0200' +test_atom tag tagger 'C O Mitter 1151968725 +0200' test_atom tag taggername 'C O Mitter' test_atom tag taggeremail '' -test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200' -test_atom tag creator 'C O Mitter 1151939925 +0200' -test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200' -test_atom tag subject 'Tagging at 1151939927' -test_atom tag contents:subject 'Tagging at 1151939927' +test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200' +test_atom tag creator 'C O Mitter 1151968725 +0200' +test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200' +test_atom tag subject 'Tagging at 1151968727' +test_atom tag contents:subject 'Tagging at 1151968727' test_atom tag body '' test_atom tag contents:body '' test_atom tag contents:signature '' -test_atom tag contents 'Tagging at 1151939927 +test_atom tag contents 'Tagging at 1151968727 ' test_atom tag HEAD ' ' @@ -168,16 +168,16 @@ test_date () { test_expect_success 'Check unformatted date fields output' ' test_date "" \ - "Mon Jul 3 17:18:43 2006 +0200" \ - "Mon Jul 3 17:18:44 2006 +0200" \ - "Mon Jul 3 17:18:45 2006 +0200" + "Tue Jul 4 01:18:43 2006 +0200" \ + "Tue Jul 4 01:18:44 2006 +0200" \ + "Tue Jul 4 01:18:45 2006 +0200" ' test_expect_success 'Check format "default" formatted date fields output' ' test_date default \ - "Mon Jul 3 17:18:43 2006 +0200" \ - "Mon Jul 3 17:18:44 2006 +0200" \ - "Mon Jul 3 17:18:45 2006 +0200" + "Tue Jul 4 01:18:43 2006 +0200" \ + "Tue Jul 4 01:18:44 2006 +0200" \ + "Tue Jul 4 01:18:45 2006 +0200" ' # Don't know how to do relative check because I can't know when this script @@ -191,36 +191,36 @@ test_expect_success 'Check format "relative" date fields output' ' ' test_expect_success 'Check format "short" date fields output' ' - test_date short 2006-07-03 2006-07-03 2006-07-03 + test_date short 2006-07-04 2006-07-04 2006-07-04 ' test_expect_success 'Check format "local" date fields output' ' test_date local \ - "Mon Jul 3 15:18:43 2006" \ - "Mon Jul 3 15:18:44 2006" \ - "Mon Jul 3 15:18:45 2006" + "Mon Jul 3 23:18:43 2006" \ + "Mon Jul 3 23:18:44 2006" \ + "Mon Jul 3 23:18:45 2006" ' test_expect_success 'Check format "iso8601" date fields output' ' test_date iso8601 \ - "2006-07-03 17:18:43 +0200" \ - "2006-07-03 17:18:44 +0200" \ - "2006-07-03 17:18:45 +0200" + "2006-07-04 01:18:43 +0200" \ + "2006-07-04 01:18:44 +0200" \ + "2006-07-04 01:18:45 +0200" ' test_expect_success 'Check format "rfc2822" date fields output' ' test_date rfc2822 \ - "Mon, 3 Jul 2006 17:18:43 +0200" \ - "Mon, 3 Jul 2006 17:18:44 +0200" \ - "Mon, 3 Jul 2006 17:18:45 +0200" + "Tue, 4 Jul 2006 01:18:43 +0200" \ + "Tue, 4 Jul 2006 01:18:44 +0200" \ + "Tue, 4 Jul 2006 01:18:45 +0200" ' test_expect_success 'Check format "raw" date fields output' ' - test_date raw "1151939923 +0200" "1151939924 +0200" "1151939925 +0200" + test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200" ' test_expect_success 'Check format of strftime date fields' ' - echo "my date is 2006-07-03" >expected && + echo "my date is 2006-07-04" >expected && git for-each-ref \ --format="%(authordate:format:my date is %Y-%m-%d)" \ refs/heads >actual && @@ -528,8 +528,8 @@ body contents $sig" cat >expected < refs/tags/master $(git rev-parse refs/tags/bogo) refs/tags/bogo +$(git rev-parse refs/tags/master) refs/tags/master EOF test_expect_success 'Verify sort with multiple keys' ' From 99264e93fc87bdd61f646c53878782947d476da8 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 3 Sep 2015 22:49:01 +0100 Subject: [PATCH 064/539] t6300: add tests for "-local" date formats Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 4867df4796b6d7..d6c9e627acd9d7 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -180,6 +180,10 @@ test_expect_success 'Check format "default" formatted date fields output' ' "Tue Jul 4 01:18:45 2006 +0200" ' +test_expect_success 'Check format "default-local" date fields output' ' + test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006" +' + # Don't know how to do relative check because I can't know when this script # is going to be run and can't fake the current time to git, and hence can't # provide expected output. Instead, I'll just make sure that "relative" @@ -190,10 +194,22 @@ test_expect_success 'Check format "relative" date fields output' ' git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual ' +# We just check that this is the same as "relative" for now. +test_expect_success 'Check format "relative-local" date fields output' ' + test_date relative-local \ + "$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \ + "$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \ + "$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)" +' + test_expect_success 'Check format "short" date fields output' ' test_date short 2006-07-04 2006-07-04 2006-07-04 ' +test_expect_success 'Check format "short-local" date fields output' ' + test_date short-local 2006-07-03 2006-07-03 2006-07-03 +' + test_expect_success 'Check format "local" date fields output' ' test_date local \ "Mon Jul 3 23:18:43 2006" \ @@ -208,6 +224,10 @@ test_expect_success 'Check format "iso8601" date fields output' ' "2006-07-04 01:18:45 +0200" ' +test_expect_success 'Check format "iso8601-local" date fields output' ' + test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000" +' + test_expect_success 'Check format "rfc2822" date fields output' ' test_date rfc2822 \ "Tue, 4 Jul 2006 01:18:43 +0200" \ @@ -215,10 +235,18 @@ test_expect_success 'Check format "rfc2822" date fields output' ' "Tue, 4 Jul 2006 01:18:45 +0200" ' +test_expect_success 'Check format "rfc2822-local" date fields output' ' + test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000" +' + test_expect_success 'Check format "raw" date fields output' ' test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200" ' +test_expect_success 'Check format "raw-local" date fields output' ' + test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000" +' + test_expect_success 'Check format of strftime date fields' ' echo "my date is 2006-07-04" >expected && git for-each-ref \ @@ -227,6 +255,14 @@ test_expect_success 'Check format of strftime date fields' ' test_cmp expected actual ' +test_expect_success 'Check format of strftime-local date fields' ' + echo "my date is 2006-07-03" >expected && + git for-each-ref \ + --format="%(authordate:format-local:my date is %Y-%m-%d)" \ + refs/heads >actual && + test_cmp expected actual +' + cat >expected <<\EOF refs/heads/master refs/remotes/origin/master From 507d7804c0b094889cd20f23ad9a48e2b76791f3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 4 Sep 2015 11:35:57 +0200 Subject: [PATCH 065/539] pager: don't use unsafe functions in signal handlers Since the commit a3da8821208d (pager: do wait_for_pager on signal death), we call wait_for_pager() in the pager's signal handler. The recent bug report revealed that this causes a deadlock in glibc at aborting "git log" [*1*]. When this happens, git process is left unterminated, and it can't be killed by SIGTERM but only by SIGKILL. The problem is that wait_for_pager() function does more than waiting for pager process's termination, but it does cleanups and printing errors. Unfortunately, the functions that may be used in a signal handler are very limited [*2*]. Particularly, malloc(), free() and the variants can't be used in a signal handler because they take a mutex internally in glibc. This was the cause of the deadlock above. Other than the direct calls of malloc/free, many functions calling malloc/free can't be used. strerror() is such one, either. Also the usage of fflush() and printf() in a signal handler is bad, although it seems working so far. In a safer side, we should avoid them, too. This patch tries to reduce the calls of such functions in signal handlers. wait_for_signal() takes a flag and avoids the unsafe calls. Also, finish_command_in_signal() is introduced for the same reason. There the free() calls are removed, and only waits for the children without whining at errors. [*1*] https://bugzilla.opensuse.org/show_bug.cgi?id=942297 [*2*] http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 Signed-off-by: Takashi Iwai Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- pager.c | 22 ++++++++++++++++------ run-command.c | 25 +++++++++++++++++-------- run-command.h | 1 + 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/pager.c b/pager.c index 070dc11cb0c85a..0f789c3ed4f513 100644 --- a/pager.c +++ b/pager.c @@ -14,19 +14,29 @@ static const char *pager_argv[] = { NULL, NULL }; static struct child_process pager_process = CHILD_PROCESS_INIT; -static void wait_for_pager(void) +static void wait_for_pager(int in_signal) { - fflush(stdout); - fflush(stderr); + if (!in_signal) { + fflush(stdout); + fflush(stderr); + } /* signal EOF to pager */ close(1); close(2); - finish_command(&pager_process); + if (in_signal) + finish_command_in_signal(&pager_process); + else + finish_command(&pager_process); +} + +static void wait_for_pager_atexit(void) +{ + wait_for_pager(0); } static void wait_for_pager_signal(int signo) { - wait_for_pager(); + wait_for_pager(1); sigchain_pop(signo); raise(signo); } @@ -90,7 +100,7 @@ void setup_pager(void) /* this makes sure that the parent terminates after the pager */ sigchain_push_common(wait_for_pager_signal); - atexit(wait_for_pager); + atexit(wait_for_pager_atexit); } int pager_in_use(void) diff --git a/run-command.c b/run-command.c index 4d73e90fad1591..fe116bc2b144cf 100644 --- a/run-command.c +++ b/run-command.c @@ -18,26 +18,27 @@ struct child_to_clean { static struct child_to_clean *children_to_clean; static int installed_child_cleanup_handler; -static void cleanup_children(int sig) +static void cleanup_children(int sig, int in_signal) { while (children_to_clean) { struct child_to_clean *p = children_to_clean; children_to_clean = p->next; kill(p->pid, sig); - free(p); + if (!in_signal) + free(p); } } static void cleanup_children_on_signal(int sig) { - cleanup_children(sig); + cleanup_children(sig, 1); sigchain_pop(sig); raise(sig); } static void cleanup_children_on_exit(void) { - cleanup_children(SIGTERM); + cleanup_children(SIGTERM, 0); } static void mark_child_for_cleanup(pid_t pid) @@ -232,7 +233,7 @@ static inline void set_cloexec(int fd) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } -static int wait_or_whine(pid_t pid, const char *argv0) +static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) { int status, code = -1; pid_t waiting; @@ -240,6 +241,8 @@ static int wait_or_whine(pid_t pid, const char *argv0) while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) ; /* nothing */ + if (in_signal) + return 0; if (waiting < 0) { failed_errno = errno; @@ -450,7 +453,7 @@ int start_command(struct child_process *cmd) * At this point we know that fork() succeeded, but execvp() * failed. Errors have been reported to our stderr. */ - wait_or_whine(cmd->pid, cmd->argv[0]); + wait_or_whine(cmd->pid, cmd->argv[0], 0); failed_errno = errno; cmd->pid = -1; } @@ -549,12 +552,18 @@ int start_command(struct child_process *cmd) int finish_command(struct child_process *cmd) { - int ret = wait_or_whine(cmd->pid, cmd->argv[0]); + int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0); argv_array_clear(&cmd->args); argv_array_clear(&cmd->env_array); return ret; } +int finish_command_in_signal(struct child_process *cmd) +{ + return wait_or_whine(cmd->pid, cmd->argv[0], 1); +} + + int run_command(struct child_process *cmd) { int code; @@ -785,7 +794,7 @@ int start_async(struct async *async) int finish_async(struct async *async) { #ifdef NO_PTHREADS - return wait_or_whine(async->pid, "child process"); + return wait_or_whine(async->pid, "child process", 0); #else void *ret = (void *)(intptr_t)(-1); diff --git a/run-command.h b/run-command.h index 1103805af1b01e..518663eef5d7c2 100644 --- a/run-command.h +++ b/run-command.h @@ -50,6 +50,7 @@ void child_process_init(struct child_process *); int start_command(struct child_process *); int finish_command(struct child_process *); +int finish_command_in_signal(struct child_process *); int run_command(struct child_process *); extern const char *find_hook(const char *name); From aab40438511dd2e4505c0850478e79773aa6721c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 4 Sep 2015 18:40:08 -0400 Subject: [PATCH 066/539] git_connect: clear GIT_* environment for ssh When we "switch" to another local repository to run the server side of a fetch or push, we must clear the variables in local_repo_env so that our local $GIT_DIR, etc, do not pollute the upload-pack or receive-pack that is executing in the "remote" repository. We have never done so for ssh connections. For the most part, nobody has noticed because ssh will not pass unknown environment variables by default. However, it is not out of the question for a user to configure ssh to pass along GIT_* variables using SendEnv/AcceptEnv. We can demonstrate the problem by using "git -c" on a local command and seeing its impact on a remote repository. This config ends up in $GIT_CONFIG_PARAMETERS. In the local case, the config has no impact, but in the ssh transport, it does (our test script has a fake ssh that passes through all environment variables; this isn't normal, but does simulate one possible setup). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- connect.c | 4 ++-- t/t5507-remote-environment.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100755 t/t5507-remote-environment.sh diff --git a/connect.c b/connect.c index c0144d859ae427..962f9904d43df2 100644 --- a/connect.c +++ b/connect.c @@ -721,6 +721,8 @@ struct child_process *git_connect(int fd[2], const char *url, strbuf_addch(&cmd, ' '); sq_quote_buf(&cmd, path); + /* remove repo-local variables from the environment */ + conn->env = local_repo_env; conn->in = conn->out = -1; if (protocol == PROTO_SSH) { const char *ssh; @@ -778,8 +780,6 @@ struct child_process *git_connect(int fd[2], const char *url, } argv_array_push(&conn->args, ssh_host); } else { - /* remove repo-local variables from the environment */ - conn->env = local_repo_env; conn->use_shell = 1; } argv_array_push(&conn->args, cmd.buf); diff --git a/t/t5507-remote-environment.sh b/t/t5507-remote-environment.sh new file mode 100755 index 00000000000000..e6149295b18742 --- /dev/null +++ b/t/t5507-remote-environment.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +test_description='check environment showed to remote side of transports' +. ./test-lib.sh + +test_expect_success 'set up "remote" push situation' ' + test_commit one && + git config push.default current && + git init remote +' + +test_expect_success 'set up fake ssh' ' + GIT_SSH_COMMAND="f() { + cd \"\$TRASH_DIRECTORY\" && + eval \"\$2\" + }; f" && + export GIT_SSH_COMMAND && + export TRASH_DIRECTORY +' + +# due to receive.denyCurrentBranch=true +test_expect_success 'confirm default push fails' ' + test_must_fail git push remote +' + +test_expect_success 'config does not travel over same-machine push' ' + test_must_fail git -c receive.denyCurrentBranch=false push remote +' + +test_expect_success 'config does not travel over ssh push' ' + test_must_fail git -c receive.denyCurrentBranch=false push host:remote +' + +test_done From 1fb59259054d379974c7ce470f36e15386f25311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 22 Aug 2015 07:39:58 +0700 Subject: [PATCH 067/539] path.c: delete an extra space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- path.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/path.c b/path.c index 95acbafa6883b4..a536ee329fd63c 100644 --- a/path.c +++ b/path.c @@ -431,7 +431,7 @@ const char *enter_repo(const char *path, int strict) } if (!suffix[i]) return NULL; - gitfile = read_gitfile(used_path) ; + gitfile = read_gitfile(used_path); if (gitfile) strcpy(used_path, gitfile); if (chdir(used_path)) From 1a9a23e35c1ba75187ee600fb7a1f107a2fb1893 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 14:12:45 +0100 Subject: [PATCH 068/539] t7610: don't use test_config in a subshell test_config uses test_when_finished to reset the configuration after the test, but this does not work inside a subshell. This does not cause a problem here because the first thing the next test does is to set this config variable itself, but we are about to add a check that will complain when test_when_finished is used in a subshell. In this case, "subdir" not a submodule so test_config has the same effect when run at the top level and can simply be moved out of the subshell. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 7eeb207b32b480..6f12b235b3a933 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -174,9 +174,9 @@ test_expect_success 'mergetool skips autoresolved' ' ' test_expect_success 'mergetool merges all from subdir' ' + test_config rerere.enabled false && ( cd subdir && - test_config rerere.enabled false && test_must_fail git merge master && ( yes "r" | git mergetool ../submod ) && ( yes "d" "d" | git mergetool --no-prompt ) && From c545bc6266e36fdb798cf79fd1f7ef9f39791a94 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 14:12:46 +0100 Subject: [PATCH 069/539] t5801: don't use test_when_finished in a subshell test_when_finished has no effect in a subshell. Since the cmp_marks function is only used once, inline it at its call site and move the test_when_finished invocation to the start of the test. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t5801-remote-helpers.sh | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index c9d3ed14c3a323..362b1581e09282 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -242,13 +242,6 @@ clean_mark () { sort >$(basename "$1") } -cmp_marks () { - test_when_finished "rm -rf git.marks testgit.marks" && - clean_mark ".git/testgit/$1/git.marks" && - clean_mark ".git/testgit/$1/testgit.marks" && - test_cmp git.marks testgit.marks -} - test_expect_success 'proper failure checks for fetching' ' (cd local && test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error && @@ -258,12 +251,15 @@ test_expect_success 'proper failure checks for fetching' ' ' test_expect_success 'proper failure checks for pushing' ' + test_when_finished "rm -rf local/git.marks local/testgit.marks" && (cd local && git checkout -b crash master && echo crash >>file && git commit -a -m crash && test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git push --all && - cmp_marks origin + clean_mark ".git/testgit/origin/git.marks" && + clean_mark ".git/testgit/origin/testgit.marks" && + test_cmp git.marks testgit.marks ) ' From 5fafc07fca933be10c82ba97a6fd3b28d6b3a02e Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 14:12:47 +0100 Subject: [PATCH 070/539] test-lib-functions: support "test_config -C ..." If used in a subshell, test_config cannot unset variables at the end of a test. This is a problem when testing submodules because we do not want to "cd" at to top level of a test script in order to run the command inside the submodule. Add a "-C" option to test_config (and test_unconfig) so that test_config can be kept outside subshells and still affect subrepositories. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/test-lib-functions.sh | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index e8d3c0fdbc76d9..0e80f377cec63a 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -201,7 +201,14 @@ test_chmod () { # Unset a configuration variable, but don't fail if it doesn't exist. test_unconfig () { - git config --unset-all "$@" + config_dir= + if test "$1" = -C + then + shift + config_dir=$1 + shift + fi + git ${config_dir:+-C "$config_dir"} config --unset-all "$@" config_status=$? case "$config_status" in 5) # ok, nothing to unset @@ -213,8 +220,15 @@ test_unconfig () { # Set git config, automatically unsetting it after the test is over. test_config () { - test_when_finished "test_unconfig '$1'" && - git config "$@" + config_dir= + if test "$1" = -C + then + shift + config_dir=$1 + shift + fi + test_when_finished "test_unconfig ${config_dir:+-C '$config_dir'} '$1'" && + git ${config_dir:+-C "$config_dir"} config "$@" } test_config_global () { From da568b66f1b481998bee3a2569739ca1302fd65c Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 14:12:48 +0100 Subject: [PATCH 071/539] t7800: don't use test_config in a subshell Use the new "-C" option to test_config to change the configuration in the submodule from the top level of the test so that it can be unset correctly when the test finishes. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/t7800-difftool.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index ea35a0241c201a..48c6e2bc830e74 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -492,12 +492,12 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' git submodule add ./. submod/ule && + test_config -C submod/ule diff.tool checktrees && + test_config -C submod/ule difftool.checktrees.cmd '\'' + test -d "$LOCAL" && test -d "$REMOTE" && echo good + '\'' && ( cd submod/ule && - test_config diff.tool checktrees && - test_config difftool.checktrees.cmd '\'' - test -d "$LOCAL" && test -d "$REMOTE" && echo good - '\'' && echo good >expect && git difftool --tool=checktrees --dir-diff HEAD~ >actual && test_cmp expect actual From 0968f12a99c4ac784b6b7f858003662cfaae117f Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 14:12:49 +0100 Subject: [PATCH 072/539] test-lib-functions: detect test_when_finished in subshell test_when_finished does nothing in a subshell because the change to test_cleanup does not affect the parent. There is no POSIX way to detect that we are in a subshell ($$ and $PPID are specified to remain unchanged), but we can detect it on Bash and fall back to ignoring the bug on other shells. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- t/test-lib-functions.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 0e80f377cec63a..6dffb8bcde83b8 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -736,6 +736,11 @@ test_seq () { # what went wrong. test_when_finished () { + # We cannot detect when we are in a subshell in general, but by + # doing so on Bash is better than nothing (the test will + # silently pass on other shells). + test "${BASH_SUBSHELL-0}" = 0 || + error "bug in test script: test_when_finished does nothing in a subshell" test_cleanup="{ $* } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } From c61eb4106d9ea77ba770c28a018cafa3b81b927e Mon Sep 17 00:00:00 2001 From: Alexey Shumkin Date: Sat, 5 Sep 2015 01:24:10 +0300 Subject: [PATCH 073/539] t7900-subtree: test the "space in a subdirectory name" case In common case there can be spaces in a subdirectory name. Change tests accorgingly to this statement. Also, as far as a call to the `rejoin_msg` function (in `cmd_split`) does not take into account such a case this patch fixes commit message when `--rejoin` option is set . Besides, as `fixnl` and `multiline` functions did not take into account the "new" tested "space in a subdirectory name" case they become unused and redundant, so they are removed. Signed-off-by: Alexey Shumkin Signed-off-by: Junio C Hamano --- contrib/subtree/git-subtree.sh | 2 +- contrib/subtree/t/t7900-subtree.sh | 147 +++++++++++++++-------------- 2 files changed, 76 insertions(+), 73 deletions(-) diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 9f065718513c5c..72a20c0c2d5715 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -648,7 +648,7 @@ cmd_split() debug "Merging split branch into HEAD..." latest_old=$(cache_get latest_old) git merge -s ours \ - -m "$(rejoin_msg $dir $latest_old $latest_new)" \ + -m "$(rejoin_msg "$dir" $latest_old $latest_new)" \ $latest_new >&2 || exit $? fi if [ -n "$branch" ]; then diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 90519823be3813..9979827738fa50 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -32,25 +32,6 @@ check_equal() fi } -fixnl() -{ - t="" - while read x; do - t="$t$x " - done - echo $t -} - -multiline() -{ - while read x; do - set -- $x - for d in "$@"; do - echo "$d" - done - done -} - undo() { git reset --hard HEAD~ @@ -62,11 +43,11 @@ last_commit_message() } test_expect_success 'init subproj' ' - test_create_repo subproj + test_create_repo "sub proj" ' # To the subproject! -cd subproj +cd ./"sub proj" test_expect_success 'add sub1' ' create sub1 && @@ -106,39 +87,39 @@ test_expect_success 'add main4' ' ' test_expect_success 'fetch subproj history' ' - git fetch ./subproj sub1 && + git fetch ./"sub proj" sub1 && git branch sub1 FETCH_HEAD ' test_expect_success 'no subtree exists in main tree' ' - test_must_fail git subtree merge --prefix=subdir sub1 + test_must_fail git subtree merge --prefix="sub dir" sub1 ' test_expect_success 'no pull from non-existant subtree' ' - test_must_fail git subtree pull --prefix=subdir ./subproj sub1 + test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" sub1 ' test_expect_success 'check if --message works for add' ' - git subtree add --prefix=subdir --message="Added subproject" sub1 && + git subtree add --prefix="sub dir" --message="Added subproject" sub1 && check_equal ''"$(last_commit_message)"'' "Added subproject" && undo ' test_expect_success 'check if --message works as -m and --prefix as -P' ' - git subtree add -P subdir -m "Added subproject using git subtree" sub1 && + git subtree add -P "sub dir" -m "Added subproject using git subtree" sub1 && check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" && undo ' test_expect_success 'check if --message works with squash too' ' - git subtree add -P subdir -m "Added subproject with squash" --squash sub1 && + git subtree add -P "sub dir" -m "Added subproject with squash" --squash sub1 && check_equal ''"$(last_commit_message)"'' "Added subproject with squash" && undo ' test_expect_success 'add subproj to mainline' ' - git subtree add --prefix=subdir/ FETCH_HEAD && - check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'" + git subtree add --prefix="sub dir"/ FETCH_HEAD && + check_equal ''"$(last_commit_message)"'' "Add '"'sub dir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'" ' # this shouldn't actually do anything, since FETCH_HEAD is already a parent @@ -147,7 +128,7 @@ test_expect_success 'merge fetched subproj' ' ' test_expect_success 'add main-sub5' ' - create subdir/main-sub5 && + create "sub dir/main-sub5" && git commit -m "main-sub5" ' @@ -157,29 +138,29 @@ test_expect_success 'add main6' ' ' test_expect_success 'add main-sub7' ' - create subdir/main-sub7 && + create "sub dir/main-sub7" && git commit -m "main-sub7" ' test_expect_success 'fetch new subproj history' ' - git fetch ./subproj sub2 && + git fetch ./"sub proj" sub2 && git branch sub2 FETCH_HEAD ' test_expect_success 'check if --message works for merge' ' - git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 && + git subtree merge --prefix="sub dir" -m "Merged changes from subproject" sub2 && check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" && undo ' test_expect_success 'check if --message for merge works with squash too' ' - git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 && + git subtree merge --prefix "sub dir" -m "Merged changes from subproject using squash" --squash sub2 && check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" && undo ' test_expect_success 'merge new subproj history into subdir' ' - git subtree merge --prefix=subdir FETCH_HEAD && + git subtree merge --prefix="sub dir" FETCH_HEAD && git branch pre-split && check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" && undo @@ -208,53 +189,53 @@ test_expect_success 'Check that the exists for a split' ' ' test_expect_success 'check if --message works for split+rejoin' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && + spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && git branch spl1 "$spl1" && check_equal ''"$(last_commit_message)"'' "Split & rejoin" && undo ' test_expect_success 'check split with --branch' ' - spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) && + spl1=$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin) && undo && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 && + git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr1 && check_equal ''"$(git rev-parse splitbr1)"'' "$spl1" ' test_expect_success 'check hash of split' ' - spl1=$(git subtree split --prefix subdir) && - git subtree split --prefix subdir --branch splitbr1test && + spl1=$(git subtree split --prefix "sub dir") && + git subtree split --prefix "sub dir" --branch splitbr1test && check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" && new_hash=$(git rev-parse splitbr1test~2) && check_equal ''"$new_hash"'' "$subdir_hash" ' test_expect_success 'check split with --branch for an existing branch' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && + spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && undo && git branch splitbr2 sub1 && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 && + git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr2 && check_equal ''"$(git rev-parse splitbr2)"'' "$spl1" ' test_expect_success 'check split with --branch for an incompatible branch' ' - test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir + test_must_fail git subtree split --prefix "sub dir" --onto FETCH_HEAD --branch subdir ' test_expect_success 'check split+rejoin' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && + spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && undo && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin && - check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'" + git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --rejoin && + check_equal ''"$(last_commit_message)"'' "Split '"'"'sub dir/'"'"' into commit '"'"'"$spl1"'"'"'" ' test_expect_success 'add main-sub8' ' - create subdir/main-sub8 && + create "sub dir/main-sub8" && git commit -m "main-sub8" ' # To the subproject! -cd ./subproj +cd ./"sub proj" test_expect_success 'merge split into subproj' ' git fetch .. spl1 && @@ -271,22 +252,22 @@ test_expect_success 'add sub9' ' cd .. test_expect_success 'split for sub8' ' - split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' && + split2=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir/" --rejoin)"'' && git branch split2 "$split2" ' test_expect_success 'add main-sub10' ' - create subdir/main-sub10 && + create "sub dir/main-sub10" && git commit -m "main-sub10" ' test_expect_success 'split for sub10' ' - spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' && + spl3=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --rejoin)"'' && git branch spl3 "$spl3" ' # To the subproject! -cd ./subproj +cd ./"sub proj" test_expect_success 'merge split into subproj' ' git fetch .. spl3 && @@ -295,42 +276,64 @@ test_expect_success 'merge split into subproj' ' git branch subproj-merge-spl3 ' -chkm="main4 main6" -chkms="main-sub10 main-sub5 main-sub7 main-sub8" -chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl) -chks="sub1 sub2 sub3 sub9" -chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl) +chkm="main4 +main6" +chkms="main-sub10 +main-sub5 +main-sub7 +main-sub8" +chkms_sub=$(cat < Date: Sat, 5 Sep 2015 01:24:11 +0300 Subject: [PATCH 074/539] contrib/subtree: respect spaces in a repository path Remote repository may have spaces in its path, so take it into account. Also, as far as there are no tests for the `push` command, add them. Signed-off-by: Alexey Shumkin Signed-off-by: Junio C Hamano --- contrib/subtree/git-subtree.sh | 2 +- contrib/subtree/t/t7900-subtree.sh | 47 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 72a20c0c2d5715..308b777b0aa43a 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -735,7 +735,7 @@ cmd_push() refspec=$2 echo "git push using: " $repository $refspec localrev=$(git subtree split --prefix="$prefix") || die - git push $repository $localrev:refs/heads/$refspec + git push "$repository" $localrev:refs/heads/$refspec else die "'$dir' must already exist. Try 'git subtree add'." fi diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 9979827738fa50..dfbe443deaf173 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -1,6 +1,7 @@ #!/bin/sh # # Copyright (c) 2012 Avery Pennaraum +# Copyright (c) 2015 Alexey Shumkin # test_description='Basic porcelain support for subtrees @@ -471,4 +472,50 @@ test_expect_success 'verify one file change per commit' ' )) ' +# test push + +cd ../.. + +mkdir test-push + +cd test-push + +test_expect_success 'init main' ' + test_create_repo main +' + +test_expect_success 'init sub' ' + test_create_repo "sub project" +' + +cd ./"sub project" + +test_expect_success 'add subproject' ' + create "sub project" && + git commit -m "Sub project: 1" && + git branch sub-branch-1 +' + +cd ../main + +test_expect_success 'make first commit and add subproject' ' + create "main-1" && + git commit -m "main: 1" && + git subtree add "../sub project" --prefix "sub dir" --message "Added subproject" sub-branch-1 && + check_equal "$(last_commit_message)" "Added subproject" +' + +test_expect_success 'make second commit to a subproject file and push it into a sub project' ' + create "sub dir/sub1" && + git commit -m "Sub project: 2" && + git subtree push "../sub project" --prefix "sub dir" sub-branch-1 +' + +cd ../"sub project" + +test_expect_success 'Test second commit is pushed' ' + git checkout sub-branch-1 && + check_equal "$(last_commit_message)" "Sub project: 2" +' + test_done From cbd9fc2366abd6bd4bd3a544e2fe438b53ca0d2f Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 14:39:24 +0100 Subject: [PATCH 075/539] interpret-trailers: allow running outside a repository It may be useful to run git-interpret-trailers without needing to be in a repository. Signed-off-by: John Keeping Acked-by: Christian Couder Signed-off-by: Junio C Hamano --- git.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git.c b/git.c index 5feba410cab6d9..40f9df0897b0a0 100644 --- a/git.c +++ b/git.c @@ -417,7 +417,7 @@ static struct cmd_struct commands[] = { { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY }, { "init", cmd_init_db, NO_SETUP }, { "init-db", cmd_init_db, NO_SETUP }, - { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP }, + { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY }, { "log", cmd_log, RUN_SETUP }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, From a48b409f9ccd4e1957286ba064fd3a25a9ea2b56 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 8 Sep 2015 04:33:14 -0400 Subject: [PATCH 076/539] git_connect: clarify conn->use_shell flag When executing user-specified programs, we generally always want to use a shell, for flexibility and consistency. One big exception is executing $GIT_SSH, which for historical reasons must not use a shell. Once upon a time the logic in git_connect looked like: if (protocol == PROTO_SSH) { ... setup ssh ... } else { ... setup local connection ... conn->use_shell = 1; } But over time the PROTO_SSH block has grown, and the "local" block has shrunk so that it contains only conn->use_shell; it's easy to miss at the end of the large block. Moreover, PROTO_SSH now also sometimes sets use_shell, when the new GIT_SSH_COMMAND is used. Let's just set conn->use_shell when we're setting up the "conn" struct, and unset it (with a comment) in the historical GIT_SSH case. This will make the flow easier to follow. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- connect.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/connect.c b/connect.c index 962f9904d43df2..acd39d70c8781d 100644 --- a/connect.c +++ b/connect.c @@ -723,10 +723,11 @@ struct child_process *git_connect(int fd[2], const char *url, /* remove repo-local variables from the environment */ conn->env = local_repo_env; + conn->use_shell = 1; conn->in = conn->out = -1; if (protocol == PROTO_SSH) { const char *ssh; - int putty, tortoiseplink = 0; + int putty = 0, tortoiseplink = 0; char *ssh_host = hostandport; const char *port = NULL; get_host_and_port(&ssh_host, &port); @@ -748,13 +749,17 @@ struct child_process *git_connect(int fd[2], const char *url, } ssh = getenv("GIT_SSH_COMMAND"); - if (ssh) { - conn->use_shell = 1; - putty = 0; - } else { + if (!ssh) { const char *base; char *ssh_dup; + /* + * GIT_SSH is the no-shell version of + * GIT_SSH_COMMAND (and must remain so for + * historical compatibility). + */ + conn->use_shell = 0; + ssh = getenv("GIT_SSH"); if (!ssh) ssh = "ssh"; @@ -764,8 +769,9 @@ struct child_process *git_connect(int fd[2], const char *url, tortoiseplink = !strcasecmp(base, "tortoiseplink") || !strcasecmp(base, "tortoiseplink.exe"); - putty = !strcasecmp(base, "plink") || - !strcasecmp(base, "plink.exe") || tortoiseplink; + putty = tortoiseplink || + !strcasecmp(base, "plink") || + !strcasecmp(base, "plink.exe"); free(ssh_dup); } @@ -779,8 +785,6 @@ struct child_process *git_connect(int fd[2], const char *url, argv_array_push(&conn->args, port); } argv_array_push(&conn->args, ssh_host); - } else { - conn->use_shell = 1; } argv_array_push(&conn->args, cmd.buf); From ee8838d157761acf4cc38f2378277dc894c10eb0 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 8 Sep 2015 11:57:45 -0700 Subject: [PATCH 077/539] submodule: rewrite `module_clone` shell function in C This reimplements the helper function `module_clone` in shell in C as `clone`. This functionality is needed for converting `git submodule update` later on, which we want to add threading to. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/submodule--helper.c | 132 ++++++++++++++++++++++++++++++++++++ git-submodule.sh | 78 +-------------------- 2 files changed, 134 insertions(+), 76 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index bc79c41b587086..f4c3eff179b5f2 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -8,6 +8,7 @@ #include "submodule.h" #include "submodule-config.h" #include "string-list.h" +#include "run-command.h" struct module_list { const struct cache_entry **entries; @@ -123,6 +124,136 @@ static int module_name(int argc, const char **argv, const char *prefix) return 0; } +static int clone_submodule(const char *path, const char *gitdir, const char *url, + const char *depth, const char *reference, int quiet) +{ + struct child_process cp; + child_process_init(&cp); + + argv_array_push(&cp.args, "clone"); + argv_array_push(&cp.args, "--no-checkout"); + if (quiet) + argv_array_push(&cp.args, "--quiet"); + if (depth && *depth) + argv_array_pushl(&cp.args, "--depth", depth, NULL); + if (reference && *reference) + argv_array_pushl(&cp.args, "--reference", reference, NULL); + if (gitdir && *gitdir) + argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + + argv_array_push(&cp.args, url); + argv_array_push(&cp.args, path); + + cp.git_cmd = 1; + cp.env = local_repo_env; + cp.no_stdin = 1; + + return run_command(&cp); +} + +static int module_clone(int argc, const char **argv, const char *prefix) +{ + const char *path = NULL, *name = NULL, *url = NULL; + const char *reference = NULL, *depth = NULL; + int quiet = 0; + FILE *submodule_dot_git; + char *sm_gitdir, *cwd, *p; + struct strbuf rel_path = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + + struct option module_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT_STRING(0, "path", &path, + N_("path"), + N_("where the new submodule will be cloned to")), + OPT_STRING(0, "name", &name, + N_("string"), + N_("name of the new submodule")), + OPT_STRING(0, "url", &url, + N_("string"), + N_("url where to clone the submodule from")), + OPT_STRING(0, "reference", &reference, + N_("string"), + N_("reference repository")), + OPT_STRING(0, "depth", &depth, + N_("string"), + N_("depth for shallow clones")), + OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper clone [--prefix=] [--quiet] " + "[--reference ] [--name ] [--url ]" + "[--depth ] [--] [...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_clone_options, + git_submodule_helper_usage, 0); + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); + sm_gitdir = strbuf_detach(&sb, NULL); + + if (!file_exists(sm_gitdir)) { + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet)) + die(_("clone of '%s' into submodule path '%s' failed"), + url, path); + } else { + if (safe_create_leading_directories_const(path) < 0) + die(_("could not create directory '%s'"), path); + strbuf_addf(&sb, "%s/index", sm_gitdir); + unlink_or_warn(sb.buf); + strbuf_reset(&sb); + } + + /* Write a .git file in the submodule to redirect to the superproject. */ + if (safe_create_leading_directories_const(path) < 0) + die(_("could not create directory '%s'"), path); + + if (path && *path) + strbuf_addf(&sb, "%s/.git", path); + else + strbuf_addstr(&sb, ".git"); + + if (safe_create_leading_directories_const(sb.buf) < 0) + die(_("could not create leading directories of '%s'"), sb.buf); + submodule_dot_git = fopen(sb.buf, "w"); + if (!submodule_dot_git) + die_errno(_("cannot open file '%s'"), sb.buf); + + fprintf(submodule_dot_git, "gitdir: %s\n", + relative_path(sm_gitdir, path, &rel_path)); + if (fclose(submodule_dot_git)) + die(_("could not close file %s"), sb.buf); + strbuf_reset(&sb); + strbuf_reset(&rel_path); + + cwd = xgetcwd(); + /* Redirect the worktree of the submodule in the superproject's config */ + if (!is_absolute_path(sm_gitdir)) { + strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir); + free(sm_gitdir); + sm_gitdir = strbuf_detach(&sb, NULL); + } + + strbuf_addf(&sb, "%s/%s", cwd, path); + p = git_pathdup_submodule(path, "config"); + if (!p) + die(_("could not get submodule directory for '%s'"), path); + git_config_set_in_file(p, "core.worktree", + relative_path(sb.buf, sm_gitdir, &rel_path)); + strbuf_release(&sb); + strbuf_release(&rel_path); + free(sm_gitdir); + free(cwd); + free(p); + return 0; +} struct cmd_struct { const char *cmd; @@ -132,6 +263,7 @@ struct cmd_struct { static struct cmd_struct commands[] = { {"list", module_list}, {"name", module_name}, + {"clone", module_clone}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/git-submodule.sh b/git-submodule.sh index 2be8da2396eb11..8b0eb9ad73fa7e 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -178,80 +178,6 @@ get_submodule_config () { printf '%s' "${value:-$default}" } -# -# Clone a submodule -# -# $1 = submodule path -# $2 = submodule name -# $3 = URL to clone -# $4 = reference repository to reuse (empty for independent) -# $5 = depth argument for shallow clones (empty for deep) -# -# Prior to calling, cmd_update checks that a possibly existing -# path is not a git repository. -# Likewise, cmd_add checks that path does not exist at all, -# since it is the location of a new submodule. -# -module_clone() -{ - sm_path=$1 - name=$2 - url=$3 - reference="$4" - depth="$5" - quiet= - if test -n "$GIT_QUIET" - then - quiet=-q - fi - - gitdir= - gitdir_base= - base_name=$(dirname "$name") - - gitdir=$(git rev-parse --git-dir) - gitdir_base="$gitdir/modules/$base_name" - gitdir="$gitdir/modules/$name" - - if test -d "$gitdir" - then - mkdir -p "$sm_path" - rm -f "$gitdir/index" - else - mkdir -p "$gitdir_base" - ( - clear_local_git_env - git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \ - --separate-git-dir "$gitdir" "$url" "$sm_path" - ) || - die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")" - fi - - # We already are at the root of the work tree but cd_to_toplevel will - # resolve any symlinks that might be present in $PWD - a=$(cd_to_toplevel && cd "$gitdir" && pwd)/ - b=$(cd_to_toplevel && cd "$sm_path" && pwd)/ - # Remove all common leading directories after a sanity check - if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then - die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")" - fi - while test "${a%%/*}" = "${b%%/*}" - do - a=${a#*/} - b=${b#*/} - done - # Now chop off the trailing '/'s that were added in the beginning - a=${a%/} - b=${b%/} - - # Turn each leading "*/" component into "../" - rel=$(printf '%s\n' "$b" | sed -e 's|[^/][^/]*|..|g') - printf '%s\n' "gitdir: $rel/$a" >"$sm_path/.git" - - rel=$(printf '%s\n' "$a" | sed -e 's|[^/][^/]*|..|g') - (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b") -} - isnumber() { n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" @@ -412,7 +338,7 @@ Use -f if you really want to add it." >&2 echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" fi fi - module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit + git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit ( clear_local_git_env cd "$sm_path" && @@ -774,7 +700,7 @@ Maybe you want to use 'update --init'?")" if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git then - module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit + git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit cloned_modules="$cloned_modules;$name" subsha1= else From ef49e05a6431727b54c399d1f3636c76ac682d61 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Sat, 5 Sep 2015 13:22:10 +0100 Subject: [PATCH 078/539] Makefile: fix MAKEFLAGS tests with multiple flags findstring is defined as $(findstring FIND,IN) so if multiple flags are set these tests do the wrong thing unless $(MAKEFLAGS) is the second argument. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index bab75655635b17..ef95ada06c1a09 100644 --- a/Makefile +++ b/Makefile @@ -608,13 +608,13 @@ endif QUIET_SUBDIR0 = $(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = -ifneq ($(findstring $(MAKEFLAGS),w),w) +ifneq ($(findstring w,$(MAKEFLAGS)),w) PRINT_DIR = --no-print-directory else # "make -w" NO_SUBDIR = : endif -ifneq ($(findstring $(MAKEFLAGS),s),s) +ifneq ($(findstring s,$(MAKEFLAGS)),s) ifndef V QUIET_CC = @echo ' ' CC $@; QUIET_AR = @echo ' ' AR $@; From ac179b4d9ceb460ca0b6bc77806b54b8e7dc3dd4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Sep 2015 14:27:21 -0700 Subject: [PATCH 079/539] Makefile: allow $(ARFLAGS) specified from the command line We can do this because we have a very simple needs and run "ar" exactly the same way everywhere ;-). Requested-by: Jeffrey Walton Signed-off-by: Junio C Hamano --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index e0caec3f679089..447011195c50c6 100644 --- a/Makefile +++ b/Makefile @@ -351,6 +351,9 @@ ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) STRIP ?= strip +# Create as necessary, replace existing, make ranlib unneeded. +ARFLAGS = rcs + # Among the variables below, these: # gitexecdir # template_dir @@ -2068,13 +2071,13 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIB_FILE): $(LIB_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^ + $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ $(XDIFF_LIB): $(XDIFF_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^ + $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ $(VCSSVN_LIB): $(VCSSVN_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^ + $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ export DEFAULT_EDITOR DEFAULT_PAGER From 619e3604288ab54c3694e99746661caf0f2df961 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 10 Sep 2015 23:30:51 +0100 Subject: [PATCH 080/539] rebase: support --no-autostash This is documented as an option but we don't actually accept it. Support it so that it is possible to override the "rebase.autostash" config variable. Reported-by: Daniel Hahler Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- git-rebase.sh | 5 ++++- t/t3420-rebase-autostash.sh | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/git-rebase.sh b/git-rebase.sh index 55da9db818665f..95a45938ada989 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -14,7 +14,7 @@ git-rebase --continue | --abort | --skip | --edit-todo Available options are v,verbose! display a diffstat of what changed upstream q,quiet! be quiet. implies --no-stat -autostash! automatically stash/stash pop before and after +autostash automatically stash/stash pop before and after fork-point use 'merge-base --fork-point' to refine upstream onto=! rebase onto given branch instead of upstream p,preserve-merges! try to recreate merges instead of ignoring them @@ -292,6 +292,9 @@ do --autostash) autostash=true ;; + --no-autostash) + autostash=false + ;; --verbose) verbose=t diffstat=t diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index d783f03d3fc581..944154b2e0ad7d 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -37,6 +37,16 @@ testrebase() { type=$1 dotest=$2 + test_expect_success "rebase$type: dirty worktree, --no-autostash" ' + test_config rebase.autostash true && + git reset --hard && + git checkout -b rebased-feature-branch feature-branch && + test_when_finished git branch -D rebased-feature-branch && + test_when_finished git checkout feature-branch && + echo dirty >>file3 && + test_must_fail git rebase$type --no-autostash unrelated-onto-branch + ' + test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" ' test_config rebase.autostash true && git reset --hard && From 82e0668cde032379095b0581609c73646e9eb92a Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 10 Sep 2015 23:30:52 +0100 Subject: [PATCH 081/539] Documentation/git-rebase: fix --no-autostash formatting All of the other "--option" and "--no-option" pairs in this file are formatted as separate options. Signed-off-by: John Keeping Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 924827dc2ec79f..73cba04887cb98 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -417,7 +417,8 @@ If the '--autosquash' option is enabled by default using the configuration variable `rebase.autosquash`, this option can be used to override and disable this setting. ---[no-]autostash:: +--autostash:: +--no-autostash:: Automatically create a temporary stash before the operation begins, and apply it after the operation ends. This means that you can run rebase on a dirty worktree. However, use From 35fb4d2e3d4a3cd008ec0baa077939f60a64f244 Mon Sep 17 00:00:00 2001 From: Max Kirillov Date: Mon, 14 Sep 2015 01:17:41 +0300 Subject: [PATCH 082/539] submodule refactor: use strbuf_git_path_submodule() in add_submodule_odb() Functions which directly operate submodule's object database do not handle the case when the submodule is linked worktree (which are introduced in c7b3a3d2fe). Instead of fixing the path calculation use already existing strbuf_git_path_submodule() function without changing overall behaviour. Then it will be possible to modify only that function whenever we need to change real location of submodule's repository content. Helped-by: Jeff King Signed-off-by: Max Kirillov Signed-off-by: Junio C Hamano --- submodule.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/submodule.c b/submodule.c index 245ed4dfbb6a3d..5e5a46fe2ab765 100644 --- a/submodule.c +++ b/submodule.c @@ -122,15 +122,8 @@ static int add_submodule_odb(const char *path) struct strbuf objects_directory = STRBUF_INIT; struct alternate_object_database *alt_odb; int ret = 0; - const char *git_dir; - strbuf_addf(&objects_directory, "%s/.git", path); - git_dir = read_gitfile(objects_directory.buf); - if (git_dir) { - strbuf_reset(&objects_directory); - strbuf_addstr(&objects_directory, git_dir); - } - strbuf_addstr(&objects_directory, "/objects/"); + strbuf_git_path_submodule(&objects_directory, path, "objects/"); if (!is_directory(objects_directory.buf)) { ret = -1; goto done; From 11f9dd719104a960d3e2b478477d9055141d1dbc Mon Sep 17 00:00:00 2001 From: Max Kirillov Date: Mon, 14 Sep 2015 01:17:42 +0300 Subject: [PATCH 083/539] path: implement common_dir handling in git_pathdup_submodule() When submodule is a linked worktree, "git diff --submodule" and other calls which directly access the submodule's object database do not correctly calculate its path. Fix it by changing the git_pathdup_submodule() behavior, to use either common or per-worktree directory. Do it similarly as for parent repository, but ignore the GIT_COMMON_DIR environment variable, because it would mean common directory for the parent repository and does not make sense for submodule. Also add test for functionality which uses this call. Signed-off-by: Max Kirillov Signed-off-by: Junio C Hamano --- cache.h | 1 + path.c | 22 ++++++++++++++++++---- setup.c | 17 ++++++++++++----- t/t7410-submodule-checkout-to.sh | 10 ++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/cache.h b/cache.h index 79066e57dc806d..5eb36b413ce9a2 100644 --- a/cache.h +++ b/cache.h @@ -443,6 +443,7 @@ extern char *get_object_directory(void); extern char *get_index_file(void); extern char *get_graft_file(void); extern int set_git_dir(const char *path); +extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir); extern int get_common_dir(struct strbuf *sb, const char *gitdir); extern const char *get_git_namespace(void); extern const char *strip_namespace(const char *namespaced_ref); diff --git a/path.c b/path.c index 95acbafa6883b4..22b9e0b8d8d392 100644 --- a/path.c +++ b/path.c @@ -98,7 +98,7 @@ static const char *common_list[] = { NULL }; -static void update_common_dir(struct strbuf *buf, int git_dir_len) +static void update_common_dir(struct strbuf *buf, int git_dir_len, const char *common_dir) { char *base = buf->buf + git_dir_len; const char **p; @@ -115,12 +115,16 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len) path++; is_dir = 1; } + + if (!common_dir) + common_dir = get_git_common_dir(); + if (is_dir && dir_prefix(base, path)) { - replace_dir(buf, git_dir_len, get_git_common_dir()); + replace_dir(buf, git_dir_len, common_dir); return; } if (!is_dir && !strcmp(base, path)) { - replace_dir(buf, git_dir_len, get_git_common_dir()); + replace_dir(buf, git_dir_len, common_dir); return; } } @@ -160,7 +164,7 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len) else if (git_db_env && dir_prefix(base, "objects")) replace_dir(buf, git_dir_len + 7, get_object_directory()); else if (git_common_dir_env) - update_common_dir(buf, git_dir_len); + update_common_dir(buf, git_dir_len, NULL); } static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) @@ -228,6 +232,8 @@ static void do_submodule_path(struct strbuf *buf, const char *path, const char *fmt, va_list args) { const char *git_dir; + struct strbuf git_submodule_common_dir = STRBUF_INIT; + struct strbuf git_submodule_dir = STRBUF_INIT; strbuf_addstr(buf, path); if (buf->len && buf->buf[buf->len - 1] != '/') @@ -240,9 +246,17 @@ static void do_submodule_path(struct strbuf *buf, const char *path, strbuf_addstr(buf, git_dir); } strbuf_addch(buf, '/'); + strbuf_addstr(&git_submodule_dir, buf->buf); strbuf_vaddf(buf, fmt, args); + + if (get_common_dir_noenv(&git_submodule_common_dir, git_submodule_dir.buf)) + update_common_dir(buf, git_submodule_dir.len, git_submodule_common_dir.buf); + strbuf_cleanup_path(buf); + + strbuf_release(&git_submodule_dir); + strbuf_release(&git_submodule_common_dir); } char *git_pathdup_submodule(const char *path, const char *fmt, ...) diff --git a/setup.c b/setup.c index a17c51e61d75ac..e41e5e1a82e3cb 100644 --- a/setup.c +++ b/setup.c @@ -228,15 +228,22 @@ void verify_non_filename(const char *prefix, const char *arg) } int get_common_dir(struct strbuf *sb, const char *gitdir) +{ + const char *git_env_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT); + if (git_env_common_dir) { + strbuf_addstr(sb, git_env_common_dir); + return 1; + } else { + return get_common_dir_noenv(sb, gitdir); + } +} + +int get_common_dir_noenv(struct strbuf *sb, const char *gitdir) { struct strbuf data = STRBUF_INIT; struct strbuf path = STRBUF_INIT; - const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT); int ret = 0; - if (git_common_dir) { - strbuf_addstr(sb, git_common_dir); - return 1; - } + strbuf_addf(&path, "%s/commondir", gitdir); if (file_exists(path.buf)) { if (strbuf_read_file(&data, path.buf, 0) <= 0) diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh index 3f609e8909c463..1acef32647aee6 100755 --- a/t/t7410-submodule-checkout-to.sh +++ b/t/t7410-submodule-checkout-to.sh @@ -47,4 +47,14 @@ test_expect_success 'checkout main and initialize independed clones' \ test_expect_success 'can see submodule diffs after independed cloning' \ '(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")' +test_expect_success 'checkout sub manually' \ + 'mkdir linked_submodule && + (cd clone/main && + git worktree add "$base_path/linked_submodule/main" "$rev1_hash_main") && + (cd clone/main/sub && + git worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub")' + +test_expect_success 'can see submodule diffs after manual checkout of linked submodule' \ + '(cd linked_submodule/main && git diff --submodule master"^!" | grep "file1 updated")' + test_done From c39badbb9acfdbc26a23115943bd08311e784db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Ng=E1=BB=8Dc=20Qu=C3=A2n?= Date: Tue, 15 Sep 2015 07:36:44 +0700 Subject: [PATCH 084/539] Updated Vietnamese translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Trần Ngọc Quân --- po/vi.po | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/po/vi.po b/po/vi.po index 7133c422b7363b..f0796827ceb96d 100644 --- a/po/vi.po +++ b/po/vi.po @@ -1,14 +1,14 @@ # Vietnamese translations for gitk package. # Bản dịch tiếng Việt cho gói gitk. # This file is distributed under the same license as the gitk package. -# Trần Ngọc Quân , 2013. +# Trần Ngọc Quân , 2013, 2015. # msgid "" msgstr "" "Project-Id-Version: gitk @@GIT_VERSION@@\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" -"PO-Revision-Date: 2013-12-14 14:40+0700\n" +"PO-Revision-Date: 2015-09-15 07:33+0700\n" "Last-Translator: Trần Ngọc Quân \n" "Language-Team: Vietnamese \n" "Language: vi\n" @@ -16,6 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Gtranslator 2.91.7\n" #: gitk:140 msgid "Couldn't get list of unmerged files:" @@ -60,7 +61,7 @@ msgstr "Đang đọc" #: gitk:496 gitk:4525 msgid "Reading commits..." -msgstr "Đang đọc các lần chuyển giao..." +msgstr "Đang đọc các lần chuyển giao…" #: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" @@ -117,7 +118,7 @@ msgstr "Chính" #: gitk:2080 msgid "Preferences" -msgstr "Cá nhân hóa" +msgstr "Tùy thích" #: gitk:2079 msgid "Edit" @@ -125,11 +126,11 @@ msgstr "Chỉnh sửa" #: gitk:2084 msgid "New view..." -msgstr "Thêm trình bày mới..." +msgstr "Thêm trình bày mới…" #: gitk:2085 msgid "Edit view..." -msgstr "Sửa cách trình bày..." +msgstr "Sửa cách trình bày…" #: gitk:2086 msgid "Delete view" @@ -319,7 +320,7 @@ msgstr "Hoàn lại lần chuyển giao này" #: gitk:2647 msgid "Check out this branch" -msgstr "Checkout nhánh này" +msgstr "Lấy ra nhánh này" #: gitk:2648 msgid "Remove this branch" @@ -327,7 +328,7 @@ msgstr "Gỡ bỏ nhánh này" #: gitk:2649 msgid "Copy branch name" -msgstr "" +msgstr "Chép tên nhánh" #: gitk:2656 msgid "Highlight this too" @@ -347,7 +348,7 @@ msgstr "Xem công trạng lần chuyển giao cha mẹ" #: gitk:2660 msgid "Copy path" -msgstr "" +msgstr "Chép đường dẫn" #: gitk:2667 msgid "Show origin of this line" @@ -358,7 +359,6 @@ msgid "Run git gui blame on this line" msgstr "Chạy lệnh git gui blame cho dòng này" #: gitk:3014 -#, fuzzy msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -368,9 +368,9 @@ msgid "" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" -"Gitk - phần mềm xem các lần chuyển giao dành cho git\n" +"Gitk - ứng dụng để xem các lần chuyển giao dành cho git\n" "\n" -"Bản quyền © 2005-2011 Paul Mackerras\n" +"Bản quyền © 2005-2014 Paul Mackerras\n" "\n" "Dùng và phân phối lại phần mềm này theo các điều khoản của Giấy Phép Công GNU" @@ -424,6 +424,7 @@ msgstr ", x, l\tDi chuyển tiếp trong danh sách lịch sử" #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" +"<%s-n>\tĐến cha thứ n của lần chuyển giao hiện tại trong danh sách lịch sử" #: gitk:3057 msgid "\tMove up one page in commit list" @@ -507,9 +508,8 @@ msgid "\tMove to next find hit" msgstr "\t\tDi chuyển đến chỗ gặp kế tiếp" #: gitk:3075 -#, fuzzy msgid "g\t\tGo to commit" -msgstr "\t\tChuyển đến lần chuyển giao cuối" +msgstr "g\t\tChuyển đến lần chuyển giao" #: gitk:3076 msgid "/\t\tFocus the search box" @@ -666,9 +666,8 @@ msgid "Matches all Commit Info criteria" msgstr "Khớp mọi điều kiện Thông tin Chuyển giao" #: gitk:4086 -#, fuzzy msgid "Matches no Commit Info criteria" -msgstr "Khớp mọi điều kiện Thông tin Chuyển giao" +msgstr "Khớp không điều kiện Thông tin Chuyển giao" #: gitk:4087 msgid "Changes to Files:" @@ -716,7 +715,7 @@ msgstr "Số lượng sẽ bỏ qua:" #: gitk:4097 msgid "Miscellaneous options:" -msgstr "Tuỳ chọn hỗn hợp:" +msgstr "Tùy chọn hỗn hợp:" #: gitk:4098 msgid "Strictly sort by date" @@ -971,7 +970,7 @@ msgstr "Gặp lỗi khi tạo miếng vá:" #: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" -msgstr "ID:" +msgstr "Mã số:" #: gitk:9265 msgid "Tag name:" @@ -1186,7 +1185,7 @@ msgstr "Độ rộng biểu đồ tối đa (dòng)" #: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" -msgstr "Độ rộng biểu đồ tối đa (% của bảng)" +msgstr "Độ rộng đồ thị tối đa (% của bảng)" #: gitk:11358 msgid "Show local changes" @@ -1194,7 +1193,7 @@ msgstr "Hiển thị các thay đổi nội bộ" #: gitk:11361 msgid "Auto-select SHA1 (length)" -msgstr "Tự chọn SHA1 (độ dài)" +msgstr "Tự chọn (độ dài) SHA1" #: gitk:11365 msgid "Hide remote refs" @@ -1230,7 +1229,7 @@ msgstr "Công cụ so sánh từ bên ngoài" #: gitk:11390 msgid "Choose..." -msgstr "Chọn..." +msgstr "Chọn…" #: gitk:11395 msgid "General options" @@ -1354,6 +1353,8 @@ msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." msgstr "" +"Rất tiếc, gitk không thể chạy Tcl/Tk phiên bản này.\n" +" Gitk cần ít nhất là Tcl/Tk 8.4." #: gitk:12269 msgid "Cannot find a git repository here." @@ -1366,7 +1367,7 @@ msgstr "Đối số “%s” chưa rõ ràng: vừa là điểm xét duyệt v #: gitk:12328 msgid "Bad arguments to gitk:" -msgstr "Đối số không hợp lệ cho gitk:" +msgstr "Đối số cho gitk không hợp lệ:" #~ msgid "mc" #~ msgstr "mc" From 95a4fb0eac20de024fed242a7c9227af86334202 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 15 Sep 2015 06:05:39 -0400 Subject: [PATCH 085/539] blame: handle --first-parent The revision.c options-parser will parse "--first-parent" for us, but the blame code does not actually respect it, as we simply iterate over the whole list returned by first_scapegoat(). We can fix this by returning a truncated parent list. Note that we could technically also do so by limiting the return value of num_scapegoats(), but that is less robust. We would rely on nobody ever looking at the "next" pointer from the returned list. Combining "--reverse" with "--first-parent" is more complicated, and will probably involve cooperation from revision.c. Since the desired semantics are not even clear, let's punt on this for now, but explicitly disallow it to avoid confusing users (this is not really a regression, since it did something nonsensical before). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/blame.c | 11 ++++++++++- t/annotate-tests.sh | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/builtin/blame.c b/builtin/blame.c index a22ac174078742..e024f43aeedc2c 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1365,8 +1365,15 @@ static void pass_whole_blame(struct scoreboard *sb, */ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) { - if (!reverse) + if (!reverse) { + if (revs->first_parent_only && + commit->parents && + commit->parents->next) { + free_commit_list(commit->parents->next); + commit->parents->next = NULL; + } return commit->parents; + } return lookup_decoration(&revs->children, &commit->object); } @@ -2677,6 +2684,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } else if (contents_from) die("--contents and --children do not blend well."); + else if (revs.first_parent_only) + die("combining --first-parent and --reverse is not supported"); else { final_commit_name = prepare_initial(&sb); sb.commits.compare = compare_commits_by_reverse_commit_date; diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index f5c01758ca38b5..b1673b3e8f38d9 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -111,6 +111,10 @@ test_expect_success 'blame 2 authors + 2 merged-in authors' ' check_count A 2 B 1 B1 2 B2 1 ' +test_expect_success 'blame --first-parent blames merge for branch1' ' + check_count --first-parent A 2 B 1 "A U Thor" 2 B2 1 +' + test_expect_success 'blame ancestor' ' check_count -h master A 2 B 2 ' From 00a9403a1069b19f27d690853db34459b32b3d3d Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Wed, 16 Sep 2015 14:37:04 +0200 Subject: [PATCH 086/539] git-p4: improve path encoding verbose output If a path with non-ASCII characters is detected then print the encoding and the encoded string in verbose mode. Signed-off-by: Lars Schneider Signed-off-by: Junio C Hamano --- git-p4.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/git-p4.py b/git-p4.py index b1ad86de7f2b73..65feb22f58f89e 100755 --- a/git-p4.py +++ b/git-p4.py @@ -2213,16 +2213,15 @@ def streamOneP4File(self, file, contents): text = regexp.sub(r'$\1$', text) contents = [ text ] - if gitConfig("git-p4.pathEncoding"): - relPath = relPath.decode(gitConfig("git-p4.pathEncoding")).encode('utf8', 'replace') - elif self.verbose: - try: - relPath.decode('ascii') - except: - print ( - "Path with Non-ASCII characters detected and no path encoding defined. " - "Please check the encoding: %s" % relPath - ) + try: + relPath.decode('ascii') + except: + encoding = 'utf8' + if gitConfig('git-p4.pathEncoding'): + encoding = gitConfig('git-p4.pathEncoding') + relPath = relPath.decode(encoding).encode('utf8', 'replace') + if self.verbose: + print 'Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, relPath) self.gitStream.write("M %s inline %s\n" % (git_mode, relPath)) From e6f2599cbade92bcbb831d8e4845ab6f6211176c Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Thu, 17 Sep 2015 18:28:33 +0200 Subject: [PATCH 087/539] strtoul_ui: reject negative values strtoul_ui uses strtoul to get a long unsigned, then checks that casting to unsigned does not lose information and return the casted value. On 64 bits architecture, checking that the cast does not change the value catches most errors, but when sizeof(int) == sizeof(long) (e.g. i386), the check does nothing. Unfortunately, strtoul silently accepts negative values, and as a result strtoul_ui("-1", ...) raised no error. This patch catches negative values before it's too late, i.e. before calling strtoul. Reported-by: Max Kirillov Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- git-compat-util.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index c6d391f86490b9..4515c494ee3873 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -812,6 +812,9 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result) char *p; errno = 0; + /* negative values would be accepted by strtoul */ + if (strchr(s, '-')) + return -1; ul = strtoul(s, &p, base); if (errno || *p || p == s || (unsigned int) ul != ul) return -1; From 3a25761a5eb01d797f85621b5844a4eea5a02377 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Sat, 22 Aug 2015 09:09:37 +0530 Subject: [PATCH 088/539] ref-filter: move `struct atom_value` to ref-filter.c Since atom_value is only required for the internal working of ref-filter it doesn't belong in the public header. Helped-by: Eric Sunshine Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- ref-filter.c | 5 +++++ ref-filter.h | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index 46963a5a421255..e53c77e86b9992 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -55,6 +55,11 @@ static struct { { "color" }, }; +struct atom_value { + const char *s; + unsigned long ul; /* used for sorting when not FIELD_STR */ +}; + /* * An atom is a valid field atom listed above, possibly prefixed with * a "*" to denote deref_tag(). diff --git a/ref-filter.h b/ref-filter.h index 6bf27d8bc4a0b3..45026d04d6d0a0 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -16,10 +16,7 @@ #define FILTER_REFS_INCLUDE_BROKEN 0x1 #define FILTER_REFS_ALL 0x2 -struct atom_value { - const char *s; - unsigned long ul; /* used for sorting when not FIELD_STR */ -}; +struct atom_value; struct ref_sorting { struct ref_sorting *next; From 574e96a2418cce16934f2b6e20a0af5ff5f85c92 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:18 +0530 Subject: [PATCH 089/539] ref-filter: introduce ref_formatting_state and ref_formatting_stack Introduce ref_formatting_state which will hold the formatted output strbuf instead of directly printing to stdout. This will help us in creating modifier atoms which modify the format specified before printing to stdout. Implement a stack machinery for ref_formatting_state, this allows us to push and pop elements onto the stack. Whenever we pop an element from the stack, the strbuf from that element is appended to the strbuf of the next element on the stack, this will allow us to support nesting of modifier atoms. Rename some functions to reflect the changes made: print_value() -> append_atom() emit() -> append_literal() Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- ref-filter.c | 78 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index e53c77e86b9992..432cea0265062b 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -55,6 +55,18 @@ static struct { { "color" }, }; +#define REF_FORMATTING_STATE_INIT { 0, NULL } + +struct ref_formatting_stack { + struct ref_formatting_stack *prev; + struct strbuf output; +}; + +struct ref_formatting_state { + int quote_style; + struct ref_formatting_stack *stack; +}; + struct atom_value { const char *s; unsigned long ul; /* used for sorting when not FIELD_STR */ @@ -129,6 +141,27 @@ int parse_ref_filter_atom(const char *atom, const char *ep) return at; } +static void push_stack_element(struct ref_formatting_stack **stack) +{ + struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack)); + + strbuf_init(&s->output, 0); + s->prev = *stack; + *stack = s; +} + +static void pop_stack_element(struct ref_formatting_stack **stack) +{ + struct ref_formatting_stack *current = *stack; + struct ref_formatting_stack *prev = current->prev; + + if (prev) + strbuf_addbuf(&prev->output, ¤t->output); + strbuf_release(¤t->output); + free(current); + *stack = prev; +} + /* * In a format string, find the next occurrence of %(atom). */ @@ -1195,30 +1228,27 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs); } -static void print_value(struct atom_value *v, int quote_style) +static void append_atom(struct atom_value *v, struct ref_formatting_state *state) { - struct strbuf sb = STRBUF_INIT; - switch (quote_style) { + struct strbuf *s = &state->stack->output; + + switch (state->quote_style) { case QUOTE_NONE: - fputs(v->s, stdout); + strbuf_addstr(s, v->s); break; case QUOTE_SHELL: - sq_quote_buf(&sb, v->s); + sq_quote_buf(s, v->s); break; case QUOTE_PERL: - perl_quote_buf(&sb, v->s); + perl_quote_buf(s, v->s); break; case QUOTE_PYTHON: - python_quote_buf(&sb, v->s); + python_quote_buf(s, v->s); break; case QUOTE_TCL: - tcl_quote_buf(&sb, v->s); + tcl_quote_buf(s, v->s); break; } - if (quote_style != QUOTE_NONE) { - fputs(sb.buf, stdout); - strbuf_release(&sb); - } } static int hex1(char ch) @@ -1239,8 +1269,10 @@ static int hex2(const char *cp) return -1; } -static void emit(const char *cp, const char *ep) +static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) { + struct strbuf *s = &state->stack->output; + while (*cp && (!ep || cp < ep)) { if (*cp == '%') { if (cp[1] == '%') @@ -1248,13 +1280,13 @@ static void emit(const char *cp, const char *ep) else { int ch = hex2(cp + 1); if (0 <= ch) { - putchar(ch); + strbuf_addch(s, ch); cp += 3; continue; } } } - putchar(*cp); + strbuf_addch(s, *cp); cp++; } } @@ -1262,19 +1294,24 @@ static void emit(const char *cp, const char *ep) void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) { const char *cp, *sp, *ep; + struct strbuf *final_buf; + struct ref_formatting_state state = REF_FORMATTING_STATE_INIT; + + state.quote_style = quote_style; + push_stack_element(&state.stack); for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { struct atom_value *atomv; ep = strchr(sp, ')'); if (cp < sp) - emit(cp, sp); + append_literal(cp, sp, &state); get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv); - print_value(atomv, quote_style); + append_atom(atomv, &state); } if (*cp) { sp = cp + strlen(cp); - emit(cp, sp); + append_literal(cp, sp, &state); } if (need_color_reset_at_eol) { struct atom_value resetv; @@ -1283,8 +1320,11 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu if (color_parse("reset", color) < 0) die("BUG: couldn't parse 'reset' as a color"); resetv.s = color; - print_value(&resetv, quote_style); + append_atom(&resetv, &state); } + final_buf = &state.stack->output; + fwrite(final_buf->buf, 1, final_buf->len, stdout); + pop_stack_element(&state.stack); putchar('\n'); } From 110dcda50d5ddaf3557666eea3b012a6ccc74dce Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:19 +0530 Subject: [PATCH 090/539] utf8: add function to align a string into given strbuf Add strbuf_utf8_align() which will align a given string into a strbuf as per given align_type and width. If the width is greater than the string length then no alignment is performed. Helped-by: Eric Sunshine Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- utf8.c | 21 +++++++++++++++++++++ utf8.h | 15 +++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/utf8.c b/utf8.c index 28e6d76a425db4..00e10c86ad7ea7 100644 --- a/utf8.c +++ b/utf8.c @@ -644,3 +644,24 @@ int skip_utf8_bom(char **text, size_t len) *text += strlen(utf8_bom); return 1; } + +void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width, + const char *s) +{ + int slen = strlen(s); + int display_len = utf8_strnwidth(s, slen, 0); + int utf8_compensation = slen - display_len; + + if (display_len >= width) { + strbuf_addstr(buf, s); + return; + } + + if (position == ALIGN_LEFT) + strbuf_addf(buf, "%-*s", width + utf8_compensation, s); + else if (position == ALIGN_MIDDLE) { + int left = (width - display_len) / 2; + strbuf_addf(buf, "%*s%-*s", left, "", width - left + utf8_compensation, s); + } else if (position == ALIGN_RIGHT) + strbuf_addf(buf, "%*s", width + utf8_compensation, s); +} diff --git a/utf8.h b/utf8.h index 5a9e94bee62fd2..7930b44f19c701 100644 --- a/utf8.h +++ b/utf8.h @@ -55,4 +55,19 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding); */ int is_hfs_dotgit(const char *path); +typedef enum { + ALIGN_LEFT, + ALIGN_MIDDLE, + ALIGN_RIGHT +} align_type; + +/* + * Align the string given and store it into a strbuf as per the + * 'position' and 'width'. If the given string length is larger than + * 'width' than then the input string is not truncated and no + * alignment is done. + */ +void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width, + const char *s); + #endif From 63d89fbce11c358ab73bdbb0d36919a454b2f5a6 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:20 +0530 Subject: [PATCH 091/539] ref-filter: introduce handler function for each atom Introduce a handler function for each atom, which is called when the atom is processed in show_ref_array_item(). In this context make append_atom() as the default handler function and extract quote_formatting() out of append_atom(). Bump this to the top. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- ref-filter.c | 54 +++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index 432cea0265062b..a99321633be359 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -69,6 +69,7 @@ struct ref_formatting_state { struct atom_value { const char *s; + void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); unsigned long ul; /* used for sorting when not FIELD_STR */ }; @@ -141,6 +142,32 @@ int parse_ref_filter_atom(const char *atom, const char *ep) return at; } +static void quote_formatting(struct strbuf *s, const char *str, int quote_style) +{ + switch (quote_style) { + case QUOTE_NONE: + strbuf_addstr(s, str); + break; + case QUOTE_SHELL: + sq_quote_buf(s, str); + break; + case QUOTE_PERL: + perl_quote_buf(s, str); + break; + case QUOTE_PYTHON: + python_quote_buf(s, str); + break; + case QUOTE_TCL: + tcl_quote_buf(s, str); + break; + } +} + +static void append_atom(struct atom_value *v, struct ref_formatting_state *state) +{ + quote_formatting(&state->stack->output, v->s, state->quote_style); +} + static void push_stack_element(struct ref_formatting_stack **stack) { struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack)); @@ -662,6 +689,8 @@ static void populate_value(struct ref_array_item *ref) const char *formatp; struct branch *branch = NULL; + v->handler = append_atom; + if (*name == '*') { deref = 1; name++; @@ -1228,29 +1257,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs); } -static void append_atom(struct atom_value *v, struct ref_formatting_state *state) -{ - struct strbuf *s = &state->stack->output; - - switch (state->quote_style) { - case QUOTE_NONE: - strbuf_addstr(s, v->s); - break; - case QUOTE_SHELL: - sq_quote_buf(s, v->s); - break; - case QUOTE_PERL: - perl_quote_buf(s, v->s); - break; - case QUOTE_PYTHON: - python_quote_buf(s, v->s); - break; - case QUOTE_TCL: - tcl_quote_buf(s, v->s); - break; - } -} - static int hex1(char ch) { if ('0' <= ch && ch <= '9') @@ -1307,7 +1313,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu if (cp < sp) append_literal(cp, sp, &state); get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv); - append_atom(atomv, &state); + atomv->handler(atomv, &state); } if (*cp) { sp = cp + strlen(cp); From 40a7551d25efac7979ee1a675c05e67313205444 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Fri, 11 Sep 2015 20:29:47 +0530 Subject: [PATCH 092/539] ref-filter: introduce match_atom_name() Introduce match_atom_name() which helps in checking if a particular atom is the atom we're looking for and if it has a value attached to it or not. Use it instead of starts_with() for checking the value of %(color:...) atom. Write a test for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Thanks-to: Junio C Hamano Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- ref-filter.c | 23 +++++++++++++++++++++-- t/t6302-for-each-ref-filter.sh | 4 ++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index a99321633be359..514de3404664d0 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -189,6 +189,22 @@ static void pop_stack_element(struct ref_formatting_stack **stack) *stack = prev; } +static int match_atom_name(const char *name, const char *atom_name, const char **val) +{ + const char *body; + + if (!skip_prefix(name, atom_name, &body)) + return 0; /* doesn't even begin with "atom_name" */ + if (!body[0]) { + *val = NULL; /* %(atom_name) and no customization */ + return 1; + } + if (body[0] != ':') + return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */ + *val = body + 1; /* "atom_name:val" */ + return 1; +} + /* * In a format string, find the next occurrence of %(atom). */ @@ -687,6 +703,7 @@ static void populate_value(struct ref_array_item *ref) int deref = 0; const char *refname; const char *formatp; + const char *valp; struct branch *branch = NULL; v->handler = append_atom; @@ -721,10 +738,12 @@ static void populate_value(struct ref_array_item *ref) refname = branch_get_push(branch, NULL); if (!refname) continue; - } else if (starts_with(name, "color:")) { + } else if (match_atom_name(name, "color", &valp)) { char color[COLOR_MAXLEN] = ""; - if (color_parse(name + 6, color) < 0) + if (!valp) + die(_("expected format: %%(color:)")); + if (color_parse(valp, color) < 0) die(_("unable to parse format")); v->s = xstrdup(color); continue; diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 505a3601610be4..c4f0378d4cd25a 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -81,4 +81,8 @@ test_expect_success 'filtering with --contains' ' test_cmp expect actual ' +test_expect_success '%(color) must fail' ' + test_must_fail git for-each-ref --format="%(color)%(refname)" +' + test_done From ce59208293f793ae822b5bfdf4040739f1c3ccb7 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Fri, 11 Sep 2015 20:33:07 +0530 Subject: [PATCH 093/539] ref-filter: implement an `align` atom Implement an `align` atom which left-, middle-, or right-aligns the content between %(align:...) and %(end). The "align:" is followed by `` and `` in any order separated by a comma, where the `` is either left, right or middle, default being left and `` is the total length of the content with alignment. If the contents length is more than the width then no alignment is performed. e.g. to align a refname atom to the middle with a total width of 40 we can do: --format="%(align:middle,40)%(refname)%(end)". We introduce an `at_end` function for each element of the stack which is to be called when the `end` atom is encountered. Using this we implement end_align_handler() for the `align` atom, this aligns the final strbuf by calling `strbuf_utf8_align()` from utf8.c. Ensure that quote formatting is performed on the whole of %(align:...)...%(end) rather than individual atoms inside. We skip quote formatting for individual atoms when the current stack element is handling an %(align:...) atom and perform quote formatting at the end when we encounter the %(end) atom of the second element of then stack. Add documentation and tests for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Helped-by: Junio C Hamano Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 11 +++ ref-filter.c | 110 ++++++++++++++++++++++++++++- t/t6302-for-each-ref-filter.sh | 82 +++++++++++++++++++++ 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index e49d5782fc6f82..3a271bf8afaa08 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -127,6 +127,17 @@ color:: Change output color. Followed by `:`, where names are described in `color.branch.*`. +align:: + Left-, middle-, or right-align the content between + %(align:...) and %(end). The "align:" is followed by `` + and `` in any order separated by a comma, where the + `` is either left, right or middle, default being + left and `` is the total length of the content with + alignment. If the contents length is more than the width then + no alignment is performed. If used with '--quote' everything + in between %(align:...) and %(end) is quoted, but if nested + then only the topmost level performs quoting. + In addition to the above, for commit and tag objects, the header field names (`tree`, `parent`, `object`, `type`, and `tag`) can be used to specify the value in the header field. diff --git a/ref-filter.c b/ref-filter.c index 514de3404664d0..c65cd60fe52d10 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -10,6 +10,7 @@ #include "quote.h" #include "ref-filter.h" #include "revision.h" +#include "utf8.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; @@ -53,13 +54,22 @@ static struct { { "flag" }, { "HEAD" }, { "color" }, + { "align" }, + { "end" }, }; #define REF_FORMATTING_STATE_INIT { 0, NULL } +struct align { + align_type position; + unsigned int width; +}; + struct ref_formatting_stack { struct ref_formatting_stack *prev; struct strbuf output; + void (*at_end)(struct ref_formatting_stack *stack); + void *at_end_data; }; struct ref_formatting_state { @@ -69,6 +79,9 @@ struct ref_formatting_state { struct atom_value { const char *s; + union { + struct align align; + } u; void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); unsigned long ul; /* used for sorting when not FIELD_STR */ }; @@ -165,7 +178,16 @@ static void quote_formatting(struct strbuf *s, const char *str, int quote_style) static void append_atom(struct atom_value *v, struct ref_formatting_state *state) { - quote_formatting(&state->stack->output, v->s, state->quote_style); + /* + * Quote formatting is only done when the stack has a single + * element. Otherwise quote formatting is done on the + * element's entire output strbuf when the %(end) atom is + * encountered. + */ + if (!state->stack->prev) + quote_formatting(&state->stack->output, v->s, state->quote_style); + else + strbuf_addstr(&state->stack->output, v->s); } static void push_stack_element(struct ref_formatting_stack **stack) @@ -189,6 +211,48 @@ static void pop_stack_element(struct ref_formatting_stack **stack) *stack = prev; } +static void end_align_handler(struct ref_formatting_stack *stack) +{ + struct align *align = (struct align *)stack->at_end_data; + struct strbuf s = STRBUF_INIT; + + strbuf_utf8_align(&s, align->position, align->width, stack->output.buf); + strbuf_swap(&stack->output, &s); + strbuf_release(&s); +} + +static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *new; + + push_stack_element(&state->stack); + new = state->stack; + new->at_end = end_align_handler; + new->at_end_data = &atomv->u.align; +} + +static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *current = state->stack; + struct strbuf s = STRBUF_INIT; + + if (!current->at_end) + die(_("format: %%(end) atom used without corresponding atom")); + current->at_end(current); + + /* + * Perform quote formatting when the stack element is that of + * a supporting atom. If nested then perform quote formatting + * only on the topmost supporting atom. + */ + if (!state->stack->prev->prev) { + quote_formatting(&s, current->output.buf, state->quote_style); + strbuf_swap(¤t->output, &s); + } + strbuf_release(&s); + pop_stack_element(&state->stack); +} + static int match_atom_name(const char *name, const char *atom_name, const char **val) { const char *body; @@ -773,6 +837,48 @@ static void populate_value(struct ref_array_item *ref) else v->s = " "; continue; + } else if (match_atom_name(name, "align", &valp)) { + struct align *align = &v->u.align; + struct strbuf **s, **to_free; + int width = -1; + + if (!valp) + die(_("expected format: %%(align:,)")); + + /* + * TODO: Implement a function similar to strbuf_split_str() + * which would omit the separator from the end of each value. + */ + s = to_free = strbuf_split_str(valp, ',', 0); + + align->position = ALIGN_LEFT; + + while (*s) { + /* Strip trailing comma */ + if (s[1]) + strbuf_setlen(s[0], s[0]->len - 1); + if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width)) + ; + else if (!strcmp(s[0]->buf, "left")) + align->position = ALIGN_LEFT; + else if (!strcmp(s[0]->buf, "right")) + align->position = ALIGN_RIGHT; + else if (!strcmp(s[0]->buf, "middle")) + align->position = ALIGN_MIDDLE; + else + die(_("improper format entered align:%s"), s[0]->buf); + s++; + } + + if (width < 0) + die(_("positive width expected with the %%(align) atom")); + align->width = width; + strbuf_list_free(to_free); + v->handler = align_atom_handler; + continue; + } else if (!strcmp(name, "end")) { + v->handler = end_atom_handler; + continue; } else continue; @@ -1347,6 +1453,8 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu resetv.s = color; append_atom(&resetv, &state); } + if (state.stack->prev) + die(_("format: %%(end) atom missing")); final_buf = &state.stack->output; fwrite(final_buf->buf, 1, final_buf->len, stdout); pop_stack_element(&state.stack); diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index c4f0378d4cd25a..f596035b0b10ab 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -85,4 +85,86 @@ test_expect_success '%(color) must fail' ' test_must_fail git for-each-ref --format="%(color)%(refname)" ' +test_expect_success 'left alignment is default' ' + cat >expect <<-\EOF && + refname is refs/heads/master |refs/heads/master + refname is refs/heads/side |refs/heads/side + refname is refs/odd/spot |refs/odd/spot + refname is refs/tags/double-tag|refs/tags/double-tag + refname is refs/tags/four |refs/tags/four + refname is refs/tags/one |refs/tags/one + refname is refs/tags/signed-tag|refs/tags/signed-tag + refname is refs/tags/three |refs/tags/three + refname is refs/tags/two |refs/tags/two + EOF + git for-each-ref --format="%(align:30)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual +' + +test_expect_success 'middle alignment' ' + cat >expect <<-\EOF && + | refname is refs/heads/master |refs/heads/master + | refname is refs/heads/side |refs/heads/side + | refname is refs/odd/spot |refs/odd/spot + |refname is refs/tags/double-tag|refs/tags/double-tag + | refname is refs/tags/four |refs/tags/four + | refname is refs/tags/one |refs/tags/one + |refname is refs/tags/signed-tag|refs/tags/signed-tag + | refname is refs/tags/three |refs/tags/three + | refname is refs/tags/two |refs/tags/two + EOF + git for-each-ref --format="|%(align:middle,30)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual +' + +test_expect_success 'right alignment' ' + cat >expect <<-\EOF && + | refname is refs/heads/master|refs/heads/master + | refname is refs/heads/side|refs/heads/side + | refname is refs/odd/spot|refs/odd/spot + |refname is refs/tags/double-tag|refs/tags/double-tag + | refname is refs/tags/four|refs/tags/four + | refname is refs/tags/one|refs/tags/one + |refname is refs/tags/signed-tag|refs/tags/signed-tag + | refname is refs/tags/three|refs/tags/three + | refname is refs/tags/two|refs/tags/two + EOF + git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual +' + +# Individual atoms inside %(align:...) and %(end) must not be quoted. + +test_expect_success 'alignment with format quote' " + cat >expect <<-\EOF && + |' '\''master| A U Thor'\'' '| + |' '\''side| A U Thor'\'' '| + |' '\''odd/spot| A U Thor'\'' '| + |' '\''double-tag| '\'' '| + |' '\''four| A U Thor'\'' '| + |' '\''one| A U Thor'\'' '| + |' '\''signed-tag| '\'' '| + |' '\''three| A U Thor'\'' '| + |' '\''two| A U Thor'\'' '| + EOF + git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual && + test_cmp expect actual +" + +test_expect_success 'nested alignment with quote formatting' " + cat >expect <<-\EOF && + |' master '| + |' side '| + |' odd/spot '| + |' double-tag '| + |' four '| + |' one '| + |' signed-tag '| + |' three '| + |' two '| + EOF + git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual && + test_cmp expect actual +" + test_done From 5b4f28510f36062134390ad8818721c1d4a2760f Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:23 +0530 Subject: [PATCH 094/539] ref-filter: add option to filter out tags, branches and remotes Add a function called 'for_each_fullref_in()' to refs.{c,h} which iterates through each ref for the given path without trimming the path and also accounting for broken refs, if mentioned. Add 'filter_ref_kind()' in ref-filter.c to check the kind of ref being handled and return the kind to 'ref_filter_handler()', where we discard refs which we do not need and assign the kind to needed refs. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- ref-filter.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++---- ref-filter.h | 13 +++++++++-- refs.c | 9 ++++++++ refs.h | 1 + 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index c65cd60fe52d10..f046d826bde847 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1190,6 +1190,34 @@ static struct ref_array_item *new_ref_array_item(const char *refname, return ref; } +static int filter_ref_kind(struct ref_filter *filter, const char *refname) +{ + unsigned int i; + + static struct { + const char *prefix; + unsigned int kind; + } ref_kind[] = { + { "refs/heads/" , FILTER_REFS_BRANCHES }, + { "refs/remotes/" , FILTER_REFS_REMOTES }, + { "refs/tags/", FILTER_REFS_TAGS} + }; + + if (filter->kind == FILTER_REFS_BRANCHES || + filter->kind == FILTER_REFS_REMOTES || + filter->kind == FILTER_REFS_TAGS) + return filter->kind; + else if (!strcmp(refname, "HEAD")) + return FILTER_REFS_DETACHED_HEAD; + + for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { + if (starts_with(refname, ref_kind[i].prefix)) + return ref_kind[i].kind; + } + + return FILTER_REFS_OTHERS; +} + /* * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. @@ -1200,6 +1228,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, struct ref_filter *filter = ref_cbdata->filter; struct ref_array_item *ref; struct commit *commit = NULL; + unsigned int kind; if (flag & REF_BAD_NAME) { warning("ignoring ref with broken name %s", refname); @@ -1211,6 +1240,11 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, return 0; } + /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */ + kind = filter_ref_kind(filter, refname); + if (!(kind & filter->kind)) + return 0; + if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname)) return 0; @@ -1242,6 +1276,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1); ref_cbdata->array->items[ref_cbdata->array->nr++] = ref; + ref->kind = kind; return 0; } @@ -1318,17 +1353,37 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int { struct ref_filter_cbdata ref_cbdata; int ret = 0; + unsigned int broken = 0; ref_cbdata.array = array; ref_cbdata.filter = filter; + if (type & FILTER_REFS_INCLUDE_BROKEN) + broken = 1; + filter->kind = type & FILTER_REFS_KIND_MASK; + /* Simple per-ref filtering */ - if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN)) - ret = for_each_rawref(ref_filter_handler, &ref_cbdata); - else if (type & FILTER_REFS_ALL) - ret = for_each_ref(ref_filter_handler, &ref_cbdata); - else if (type) + if (!filter->kind) die("filter_refs: invalid type"); + else { + /* + * For common cases where we need only branches or remotes or tags, + * we only iterate through those refs. If a mix of refs is needed, + * we iterate over all refs and filter out required refs with the help + * of filter_ref_kind(). + */ + if (filter->kind == FILTER_REFS_BRANCHES) + ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind == FILTER_REFS_REMOTES) + ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind == FILTER_REFS_TAGS) + ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind & FILTER_REFS_ALL) + ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken); + if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) + head_ref(ref_filter_handler, &ref_cbdata); + } + /* Filters that need revision walking */ if (filter->merge_commit) diff --git a/ref-filter.h b/ref-filter.h index 45026d04d6d0a0..0913ba99d6a342 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -13,8 +13,15 @@ #define QUOTE_PYTHON 4 #define QUOTE_TCL 8 -#define FILTER_REFS_INCLUDE_BROKEN 0x1 -#define FILTER_REFS_ALL 0x2 +#define FILTER_REFS_INCLUDE_BROKEN 0x0001 +#define FILTER_REFS_TAGS 0x0002 +#define FILTER_REFS_BRANCHES 0x0004 +#define FILTER_REFS_REMOTES 0x0008 +#define FILTER_REFS_OTHERS 0x0010 +#define FILTER_REFS_ALL (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \ + FILTER_REFS_REMOTES | FILTER_REFS_OTHERS) +#define FILTER_REFS_DETACHED_HEAD 0x0020 +#define FILTER_REFS_KIND_MASK (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD) struct atom_value; @@ -27,6 +34,7 @@ struct ref_sorting { struct ref_array_item { unsigned char objectname[20]; int flag; + unsigned int kind; const char *symref; struct commit *commit; struct atom_value *value; @@ -51,6 +59,7 @@ struct ref_filter { struct commit *merge_commit; unsigned int with_commit_tag_algo : 1; + unsigned int kind; }; struct ref_filter_cbdata { diff --git a/refs.c b/refs.c index 84b1aff8421de0..943ac5ef519538 100644 --- a/refs.c +++ b/refs.c @@ -2108,6 +2108,15 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data); } +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) +{ + unsigned int flag = 0; + + if (broken) + flag = DO_FOR_EACH_INCLUDE_BROKEN; + return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data); +} + int for_each_ref_in_submodule(const char *submodule, const char *prefix, each_ref_fn fn, void *cb_data) { diff --git a/refs.h b/refs.h index 6a3fa6d41d50a7..02676498328d34 100644 --- a/refs.h +++ b/refs.h @@ -173,6 +173,7 @@ typedef int each_ref_fn(const char *refname, extern int head_ref(each_ref_fn fn, void *cb_data); extern int for_each_ref(each_ref_fn fn, void *cb_data); extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); +extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken); extern int for_each_tag_ref(each_ref_fn fn, void *cb_data); extern int for_each_branch_ref(each_ref_fn fn, void *cb_data); extern int for_each_remote_ref(each_ref_fn fn, void *cb_data); From 1bb38e5a6a8fb6cf9b882a1a7038d649ceba0085 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Fri, 11 Sep 2015 20:34:16 +0530 Subject: [PATCH 095/539] ref-filter: add support for %(contents:lines=X) In 'tag.c' we can print N lines from the annotation of the tag using the '-n' option. Copy code from 'tag.c' to 'ref-filter' and modify it to support appending of N lines from the annotation of tags to the given strbuf. Implement %(contents:lines=X) where X lines of the given object are obtained. While we're at it, remove unused "contents:" atoms from the `valid_atom` array. Add documentation and test for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 3 +- builtin/tag.c | 4 +++ ref-filter.c | 47 ++++++++++++++++++++++++--- ref-filter.h | 3 +- t/t6302-for-each-ref-filter.sh | 52 ++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 6 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 3a271bf8afaa08..324ad2c7397b78 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -150,7 +150,8 @@ The complete message in a commit and tag object is `contents`. Its first line is `contents:subject`, where subject is the concatenation of all lines of the commit message up to the first blank line. The next line is 'contents:body', where body is all of the lines after the first -blank line. Finally, the optional GPG signature is `contents:signature`. +blank line. The optional GPG signature is `contents:signature`. The +first `N` lines of the message is obtained using `contents:lines=N`. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). diff --git a/builtin/tag.c b/builtin/tag.c index 471d6b1ab8847d..b0bc1c5ad4f5a0 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -185,6 +185,10 @@ static enum contains_result contains(struct commit *candidate, return contains_test(candidate, want); } +/* + * Currently modified and used in ref-filter as append_lines(), will + * eventually be removed as we port tag.c to use ref-filter APIs. + */ static void show_tag_lines(const struct object_id *oid, int lines) { int i; diff --git a/ref-filter.c b/ref-filter.c index f046d826bde847..32aab37d36b50d 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -45,9 +45,6 @@ static struct { { "subject" }, { "body" }, { "contents" }, - { "contents:subject" }, - { "contents:body" }, - { "contents:signature" }, { "upstream" }, { "push" }, { "symref" }, @@ -65,6 +62,11 @@ struct align { unsigned int width; }; +struct contents { + unsigned int lines; + struct object_id oid; +}; + struct ref_formatting_stack { struct ref_formatting_stack *prev; struct strbuf output; @@ -81,6 +83,7 @@ struct atom_value { const char *s; union { struct align align; + struct contents contents; } u; void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); unsigned long ul; /* used for sorting when not FIELD_STR */ @@ -643,6 +646,30 @@ static void find_subpos(const char *buf, unsigned long sz, *nonsiglen = *sig - buf; } +/* + * If 'lines' is greater than 0, append that many lines from the given + * 'buf' of length 'size' to the given strbuf. + */ +static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines) +{ + int i; + const char *sp, *eol; + size_t len; + + sp = buf; + + for (i = 0; i < lines && sp < buf + size; i++) { + if (i) + strbuf_addstr(out, "\n "); + eol = memchr(sp, '\n', size - (sp - buf)); + len = eol ? eol - sp : size - (sp - buf); + strbuf_add(out, sp, len); + if (!eol) + break; + sp = eol + 1; + } +} + /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { @@ -653,6 +680,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; + const char *valp = NULL; if (!!deref != (*name == '*')) continue; if (deref) @@ -662,7 +690,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj strcmp(name, "contents") && strcmp(name, "contents:subject") && strcmp(name, "contents:body") && - strcmp(name, "contents:signature")) + strcmp(name, "contents:signature") && + !starts_with(name, "contents:lines=")) continue; if (!subpos) find_subpos(buf, sz, @@ -682,6 +711,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); + else if (skip_prefix(name, "contents:lines=", &valp)) { + struct strbuf s = STRBUF_INIT; + const char *contents_end = bodylen + bodypos - siglen; + + if (strtoul_ui(valp, 10, &v->u.contents.lines)) + die(_("positive value expected contents:lines=%s"), valp); + /* Size is the length of the message after removing the signature */ + append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines); + v->s = strbuf_detach(&s, NULL); + } } } diff --git a/ref-filter.h b/ref-filter.h index 0913ba99d6a342..ab76b22de6c32e 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -59,7 +59,8 @@ struct ref_filter { struct commit *merge_commit; unsigned int with_commit_tag_algo : 1; - unsigned int kind; + unsigned int kind, + lines; }; struct ref_filter_cbdata { diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index f596035b0b10ab..bab1f283b49284 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -167,4 +167,56 @@ test_expect_success 'nested alignment with quote formatting' " test_cmp expect actual " +test_expect_success 'check `%(contents:lines=1)`' ' + cat >expect <<-\EOF && + master |three + side |four + odd/spot |three + double-tag |Annonated doubly + four |four + one |one + signed-tag |A signed tag message + three |three + two |two + EOF + git for-each-ref --format="%(refname:short) |%(contents:lines=1)" >actual && + test_cmp expect actual +' + +test_expect_success 'check `%(contents:lines=0)`' ' + cat >expect <<-\EOF && + master | + side | + odd/spot | + double-tag | + four | + one | + signed-tag | + three | + two | + EOF + git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual && + test_cmp expect actual +' + +test_expect_success 'check `%(contents:lines=99999)`' ' + cat >expect <<-\EOF && + master |three + side |four + odd/spot |three + double-tag |Annonated doubly + four |four + one |one + signed-tag |A signed tag message + three |three + two |two + EOF + git for-each-ref --format="%(refname:short) |%(contents:lines=99999)" >actual && + test_cmp expect actual +' + +test_expect_success '`%(contents:lines=-1)` should fail' ' + test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)" +' + test_done From 90c004085cfe65e1b290e5b5fc05817ec2c596a6 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:25 +0530 Subject: [PATCH 096/539] ref-filter: add support to sort by version Add support to sort by version using the "v:refname" and "version:refname" option. This is achieved by using the 'versioncmp()' function as the comparing function for qsort. This option is included to support sorting by versions in `git tag -l` which will eventually be ported to use ref-filter APIs. Add documentation and tests for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 3 +++ ref-filter.c | 15 ++++++++----- ref-filter.h | 3 ++- t/t6302-for-each-ref-filter.sh | 36 ++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 324ad2c7397b78..16b4ac561438c2 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -157,6 +157,9 @@ For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). All other fields are used to sort in their byte-value order. +There is also an option to sort by versions, this can be done by using +the fieldname `version:refname` or its alias `v:refname`. + In any case, a field name that refers to a field inapplicable to the object referred by the ref does not cause an error. It returns an empty string instead. diff --git a/ref-filter.c b/ref-filter.c index 32aab37d36b50d..c039fc891bdfbb 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -11,6 +11,8 @@ #include "ref-filter.h" #include "revision.h" #include "utf8.h" +#include "git-compat-util.h" +#include "version.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; @@ -1439,19 +1441,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru get_ref_atom_value(a, s->atom, &va); get_ref_atom_value(b, s->atom, &vb); - switch (cmp_type) { - case FIELD_STR: + if (s->version) + cmp = versioncmp(va->s, vb->s); + else if (cmp_type == FIELD_STR) cmp = strcmp(va->s, vb->s); - break; - default: + else { if (va->ul < vb->ul) cmp = -1; else if (va->ul == vb->ul) cmp = 0; else cmp = 1; - break; } + return (s->reverse) ? -cmp : cmp; } @@ -1584,6 +1586,9 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) s->reverse = 1; arg++; } + if (skip_prefix(arg, "version:", &arg) || + skip_prefix(arg, "v:", &arg)) + s->version = 1; len = strlen(arg); s->atom = parse_ref_filter_atom(arg, arg+len); return 0; diff --git a/ref-filter.h b/ref-filter.h index ab76b22de6c32e..ef25b6ef87b437 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -28,7 +28,8 @@ struct atom_value; struct ref_sorting { struct ref_sorting *next; int atom; /* index into used_atom array (internal) */ - unsigned reverse : 1; + unsigned reverse : 1, + version : 1; }; struct ref_array_item { diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index bab1f283b49284..fe4796cc9c8b91 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -219,4 +219,40 @@ test_expect_success '`%(contents:lines=-1)` should fail' ' test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)" ' +test_expect_success 'setup for version sort' ' + test_commit foo1.3 && + test_commit foo1.6 && + test_commit foo1.10 +' + +test_expect_success 'version sort' ' + git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual && + cat >expect <<-\EOF && + foo1.3 + foo1.6 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort (shortened)' ' + git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual && + cat >expect <<-\EOF && + foo1.3 + foo1.6 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'reverse version sort' ' + git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.6 + foo1.3 + EOF + test_cmp expect actual +' + test_done From bef0e12becd72b94d6bb9e48859742f55a4afb53 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:26 +0530 Subject: [PATCH 097/539] ref-filter: add option to match literal pattern Since 'ref-filter' only has an option to match path names add an option for plain fnmatch pattern-matching. This is to support the pattern matching options which are used in `git tag -l` and `git branch -l` where we can match patterns like `git tag -l foo*` which would match all tags which has a "foo*" pattern. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/for-each-ref.c | 1 + ref-filter.c | 40 +++++++++++++++++++++++++++++++++++++--- ref-filter.h | 3 ++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 40f343b6a36fc7..4e9f6c29bf1e0c 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -68,6 +68,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); filter.name_patterns = argv; + filter.match_as_path = 1; filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); ref_array_sort(sorting, &array); diff --git a/ref-filter.c b/ref-filter.c index c039fc891bdfbb..fd839ac4b337d3 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1159,11 +1159,35 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit) return is_descendant_of(commit, filter->with_commit); } +/* + * Return 1 if the refname matches one of the patterns, otherwise 0. + * A pattern can be a literal prefix (e.g. a refname "refs/heads/master" + * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref + * matches "refs/heads/mas*", too). + */ +static int match_pattern(const char **patterns, const char *refname) +{ + /* + * When no '--format' option is given we need to skip the prefix + * for matching refs of tags and branches. + */ + (void)(skip_prefix(refname, "refs/tags/", &refname) || + skip_prefix(refname, "refs/heads/", &refname) || + skip_prefix(refname, "refs/remotes/", &refname) || + skip_prefix(refname, "refs/", &refname)); + + for (; *patterns; patterns++) { + if (!wildmatch(*patterns, refname, 0, NULL)) + return 1; + } + return 0; +} + /* * Return 1 if the refname matches one of the patterns, otherwise 0. * A pattern can be path prefix (e.g. a refname "refs/heads/master" - * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref - * matches "refs/heads/m*",too). + * matches a pattern "refs/heads/" but not "refs/heads/m") or a + * wildcard (e.g. the same ref matches "refs/heads/m*", too). */ static int match_name_as_path(const char **pattern, const char *refname) { @@ -1184,6 +1208,16 @@ static int match_name_as_path(const char **pattern, const char *refname) return 0; } +/* Return 1 if the refname matches one of the patterns, otherwise 0. */ +static int filter_pattern_match(struct ref_filter *filter, const char *refname) +{ + if (!*filter->name_patterns) + return 1; /* No pattern always matches */ + if (filter->match_as_path) + return match_name_as_path(filter->name_patterns, refname); + return match_pattern(filter->name_patterns, refname); +} + /* * Given a ref (sha1, refname), check if the ref belongs to the array * of sha1s. If the given ref is a tag, check if the given tag points @@ -1286,7 +1320,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, if (!(kind & filter->kind)) return 0; - if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname)) + if (!filter_pattern_match(filter, refname)) return 0; if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname)) diff --git a/ref-filter.h b/ref-filter.h index ef25b6ef87b437..a5cfa5e677dbdf 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -59,7 +59,8 @@ struct ref_filter { } merge; struct commit *merge_commit; - unsigned int with_commit_tag_algo : 1; + unsigned int with_commit_tag_algo : 1, + match_as_path : 1; unsigned int kind, lines; }; From ac4cc866c8cddbc25b5f213286a0269318dd2485 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:18:27 +0530 Subject: [PATCH 098/539] tag.c: use 'ref-filter' data structures Make 'tag.c' use 'ref-filter' data structures and make changes to support the new data structures. This is a part of the process of porting 'tag.c' to use 'ref-filter' APIs. This is a temporary step before porting 'tag.c' to use 'ref-filter' completely. As this is a temporary step, most of the code introduced here will be removed when 'tag.c' is ported over to use 'ref-filter' APIs. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/tag.c | 106 +++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/builtin/tag.c b/builtin/tag.c index b0bc1c5ad4f5a0..fe66f7b050ce09 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,6 +17,7 @@ #include "gpg-interface.h" #include "sha1-array.h" #include "column.h" +#include "ref-filter.h" static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u ] [-f] [-m | -F ] []"), @@ -34,15 +35,6 @@ static const char * const git_tag_usage[] = { static int tag_sort; -struct tag_filter { - const char **patterns; - int lines; - int sort; - struct string_list tags; - struct commit_list *with_commit; -}; - -static struct sha1_array points_at; static unsigned int colopts; static int match_pattern(const char **patterns, const char *ref) @@ -61,19 +53,20 @@ static int match_pattern(const char **patterns, const char *ref) * removed as we port tag.c to use the ref-filter APIs. */ static const unsigned char *match_points_at(const char *refname, - const unsigned char *sha1) + const unsigned char *sha1, + struct sha1_array *points_at) { const unsigned char *tagged_sha1 = NULL; struct object *obj; - if (sha1_array_lookup(&points_at, sha1) >= 0) + if (sha1_array_lookup(points_at, sha1) >= 0) return sha1; obj = parse_object(sha1); if (!obj) die(_("malformed object at '%s'"), refname); if (obj->type == OBJ_TAG) tagged_sha1 = ((struct tag *)obj)->tagged->sha1; - if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0) + if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0) return tagged_sha1; return NULL; } @@ -228,12 +221,24 @@ static void show_tag_lines(const struct object_id *oid, int lines) free(buf); } +static void ref_array_append(struct ref_array *array, const char *refname) +{ + size_t len = strlen(refname); + struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1); + memcpy(ref->refname, refname, len); + ref->refname[len] = '\0'; + REALLOC_ARRAY(array->items, array->nr + 1); + array->items[array->nr++] = ref; +} + static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) { - struct tag_filter *filter = cb_data; + struct ref_filter_cbdata *data = cb_data; + struct ref_array *array = data->array; + struct ref_filter *filter = data->filter; - if (match_pattern(filter->patterns, refname)) { + if (match_pattern(filter->name_patterns, refname)) { if (filter->with_commit) { struct commit *commit; @@ -244,12 +249,12 @@ static int show_reference(const char *refname, const struct object_id *oid, return 0; } - if (points_at.nr && !match_points_at(refname, oid->hash)) + if (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at)) return 0; if (!filter->lines) { - if (filter->sort) - string_list_append(&filter->tags, refname); + if (tag_sort) + ref_array_append(array, refname); else printf("%s\n", refname); return 0; @@ -264,36 +269,36 @@ static int show_reference(const char *refname, const struct object_id *oid, static int sort_by_version(const void *a_, const void *b_) { - const struct string_list_item *a = a_; - const struct string_list_item *b = b_; - return versioncmp(a->string, b->string); + const struct ref_array_item *a = *((struct ref_array_item **)a_); + const struct ref_array_item *b = *((struct ref_array_item **)b_); + return versioncmp(a->refname, b->refname); } -static int list_tags(const char **patterns, int lines, - struct commit_list *with_commit, int sort) +static int list_tags(struct ref_filter *filter, int sort) { - struct tag_filter filter; + struct ref_array array; + struct ref_filter_cbdata data; + + memset(&array, 0, sizeof(array)); + data.array = &array; + data.filter = filter; - filter.patterns = patterns; - filter.lines = lines; - filter.sort = sort; - filter.with_commit = with_commit; - memset(&filter.tags, 0, sizeof(filter.tags)); - filter.tags.strdup_strings = 1; + if (filter->lines == -1) + filter->lines = 0; - for_each_tag_ref(show_reference, (void *)&filter); + for_each_tag_ref(show_reference, &data); if (sort) { int i; if ((sort & SORT_MASK) == VERCMP_SORT) - qsort(filter.tags.items, filter.tags.nr, - sizeof(struct string_list_item), sort_by_version); + qsort(array.items, array.nr, + sizeof(struct ref_array_item *), sort_by_version); if (sort & REVERSE_SORT) - for (i = filter.tags.nr - 1; i >= 0; i--) - printf("%s\n", filter.tags.items[i].string); + for (i = array.nr - 1; i >= 0; i--) + printf("%s\n", array.items[i]->refname); else - for (i = 0; i < filter.tags.nr; i++) - printf("%s\n", filter.tags.items[i].string); - string_list_clear(&filter.tags, 0); + for (i = 0; i < array.nr; i++) + printf("%s\n", array.items[i]->refname); + ref_array_clear(&array); } return 0; } @@ -574,17 +579,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) const char *object_ref, *tag; struct create_tag_options opt; char *cleanup_arg = NULL; - int annotate = 0, force = 0, lines = -1; int create_reflog = 0; + int annotate = 0, force = 0; int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; - struct commit_list *with_commit = NULL; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; + struct ref_filter filter; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), - { OPTION_INTEGER, 'n', NULL, &lines, N_("n"), + { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), N_("print lines of each tag message"), PARSE_OPT_OPTARG, NULL, 1 }, OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'), @@ -606,14 +611,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), - OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")), - OPT_WITH(&with_commit, N_("print only tags that contain the commit")), + OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")), { OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), PARSE_OPT_NONEG, parse_opt_sort }, { - OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"), + OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), 0, parse_opt_object_name }, OPT_END() @@ -622,6 +627,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) git_config(git_tag_config, NULL); memset(&opt, 0, sizeof(opt)); + memset(&filter, 0, sizeof(filter)); + filter.lines = -1; argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); @@ -638,7 +645,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) usage_with_options(git_tag_usage, options); finalize_colopts(&colopts, -1); - if (cmdmode == 'l' && lines != -1) { + if (cmdmode == 'l' && filter.lines != -1) { if (explicitly_enable_column(colopts)) die(_("--column and -n are incompatible")); colopts = 0; @@ -651,18 +658,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix) copts.padding = 2; run_column_filter(colopts, &copts); } - if (lines != -1 && tag_sort) + if (filter.lines != -1 && tag_sort) die(_("--sort and -n are incompatible")); - ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort); + filter.name_patterns = argv; + ret = list_tags(&filter, tag_sort); if (column_active(colopts)) stop_column_filter(); return ret; } - if (lines != -1) + if (filter.lines != -1) die(_("-n option is only allowed with -l.")); - if (with_commit) + if (filter.with_commit) die(_("--contains option is only allowed with -l.")); - if (points_at.nr) + if (filter.points_at.nr) die(_("--points-at option is only allowed with -l.")); if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag); From b7cc53e92c806b73e14b03f60c17b7c29e52b4a4 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Fri, 11 Sep 2015 20:36:16 +0530 Subject: [PATCH 099/539] tag.c: use 'ref-filter' APIs Make 'tag.c' use 'ref-filter' APIs for iterating through refs, sorting and printing of refs. This removes most of the code used in 'tag.c' replacing it with calls to the 'ref-filter' library. Make 'tag.c' use the 'filter_refs()' function provided by 'ref-filter' to filter out tags based on the options set. For printing tags we use 'show_ref_array_item()' function provided by 'ref-filter'. We improve the sorting option provided by 'tag.c' by using the sorting options provided by 'ref-filter'. This causes the test 'invalid sort parameter on command line' in t7004 to fail, as 'ref-filter' throws an error for all sorting fields which are incorrect. The test is changed to reflect the same. Modify documentation for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-tag.txt | 16 +- builtin/tag.c | 345 +++++--------------------------------- t/t7004-tag.sh | 8 +- 3 files changed, 53 insertions(+), 316 deletions(-) diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 84f6496bf22845..3ac4a9672ccd42 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -13,7 +13,7 @@ SYNOPSIS [ | ] 'git tag' -d ... 'git tag' [-n[]] -l [--contains ] [--points-at ] - [--column[=] | --no-column] [--create-reflog] [...] + [--column[=] | --no-column] [--create-reflog] [--sort=] [...] 'git tag' -v ... DESCRIPTION @@ -94,14 +94,16 @@ OPTIONS using fnmatch(3)). Multiple patterns may be given; if any of them matches, the tag is shown. ---sort=:: - Sort in a specific order. Supported type is "refname" - (lexicographic order), "version:refname" or "v:refname" (tag +--sort=:: + Sort based on the key given. Prefix `-` to sort in + descending order of the value. You may use the --sort= option + multiple times, in which case the last key becomes the primary + key. Also supports "version:refname" or "v:refname" (tag names are treated as versions). The "version:refname" sort order can also be affected by the - "versionsort.prereleaseSuffix" configuration variable. Prepend - "-" to reverse sort order. When this option is not given, the - sort order defaults to the value configured for the 'tag.sort' + "versionsort.prereleaseSuffix" configuration variable. + The keys supported are the same as those in `git for-each-ref`. + Sort order defaults to the value configured for the 'tag.sort' variable if it exists, or lexicographic order otherwise. See linkgit:git-config[1]. diff --git a/builtin/tag.c b/builtin/tag.c index fe66f7b050ce09..977a18c269613f 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -28,278 +28,35 @@ static const char * const git_tag_usage[] = { NULL }; -#define STRCMP_SORT 0 /* must be zero */ -#define VERCMP_SORT 1 -#define SORT_MASK 0x7fff -#define REVERSE_SORT 0x8000 - -static int tag_sort; - static unsigned int colopts; -static int match_pattern(const char **patterns, const char *ref) -{ - /* no pattern means match everything */ - if (!*patterns) - return 1; - for (; *patterns; patterns++) - if (!wildmatch(*patterns, ref, 0, NULL)) - return 1; - return 0; -} - -/* - * This is currently duplicated in ref-filter.c, and will eventually be - * removed as we port tag.c to use the ref-filter APIs. - */ -static const unsigned char *match_points_at(const char *refname, - const unsigned char *sha1, - struct sha1_array *points_at) -{ - const unsigned char *tagged_sha1 = NULL; - struct object *obj; - - if (sha1_array_lookup(points_at, sha1) >= 0) - return sha1; - obj = parse_object(sha1); - if (!obj) - die(_("malformed object at '%s'"), refname); - if (obj->type == OBJ_TAG) - tagged_sha1 = ((struct tag *)obj)->tagged->sha1; - if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0) - return tagged_sha1; - return NULL; -} - -static int in_commit_list(const struct commit_list *want, struct commit *c) -{ - for (; want; want = want->next) - if (!hashcmp(want->item->object.sha1, c->object.sha1)) - return 1; - return 0; -} - -/* - * The entire code segment for supporting the --contains option has been - * copied over to ref-filter.{c,h}. This will be deleted evetually when - * we port tag.c to use ref-filter APIs. - */ -enum contains_result { - CONTAINS_UNKNOWN = -1, - CONTAINS_NO = 0, - CONTAINS_YES = 1 -}; - -/* - * Test whether the candidate or one of its parents is contained in the list. - * Do not recurse to find out, though, but return -1 if inconclusive. - */ -static enum contains_result contains_test(struct commit *candidate, - const struct commit_list *want) -{ - /* was it previously marked as containing a want commit? */ - if (candidate->object.flags & TMP_MARK) - return 1; - /* or marked as not possibly containing a want commit? */ - if (candidate->object.flags & UNINTERESTING) - return 0; - /* or are we it? */ - if (in_commit_list(want, candidate)) { - candidate->object.flags |= TMP_MARK; - return 1; - } - - if (parse_commit(candidate) < 0) - return 0; - - return -1; -} - -/* - * Mimicking the real stack, this stack lives on the heap, avoiding stack - * overflows. - * - * At each recursion step, the stack items points to the commits whose - * ancestors are to be inspected. - */ -struct stack { - int nr, alloc; - struct stack_entry { - struct commit *commit; - struct commit_list *parents; - } *stack; -}; - -static void push_to_stack(struct commit *candidate, struct stack *stack) -{ - int index = stack->nr++; - ALLOC_GROW(stack->stack, stack->nr, stack->alloc); - stack->stack[index].commit = candidate; - stack->stack[index].parents = candidate->parents; -} - -static enum contains_result contains(struct commit *candidate, - const struct commit_list *want) -{ - struct stack stack = { 0, 0, NULL }; - int result = contains_test(candidate, want); - - if (result != CONTAINS_UNKNOWN) - return result; - - push_to_stack(candidate, &stack); - while (stack.nr) { - struct stack_entry *entry = &stack.stack[stack.nr - 1]; - struct commit *commit = entry->commit; - struct commit_list *parents = entry->parents; - - if (!parents) { - commit->object.flags |= UNINTERESTING; - stack.nr--; - } - /* - * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful 0 or 1. - */ - else switch (contains_test(parents->item, want)) { - case CONTAINS_YES: - commit->object.flags |= TMP_MARK; - stack.nr--; - break; - case CONTAINS_NO: - entry->parents = parents->next; - break; - case CONTAINS_UNKNOWN: - push_to_stack(parents->item, &stack); - break; - } - } - free(stack.stack); - return contains_test(candidate, want); -} - -/* - * Currently modified and used in ref-filter as append_lines(), will - * eventually be removed as we port tag.c to use ref-filter APIs. - */ -static void show_tag_lines(const struct object_id *oid, int lines) -{ - int i; - unsigned long size; - enum object_type type; - char *buf, *sp, *eol; - size_t len; - - buf = read_sha1_file(oid->hash, &type, &size); - if (!buf) - die_errno("unable to read object %s", oid_to_hex(oid)); - if (type != OBJ_COMMIT && type != OBJ_TAG) - goto free_return; - if (!size) - die("an empty %s object %s?", - typename(type), oid_to_hex(oid)); - - /* skip header */ - sp = strstr(buf, "\n\n"); - if (!sp) - goto free_return; - - /* only take up to "lines" lines, and strip the signature from a tag */ - if (type == OBJ_TAG) - size = parse_signature(buf, size); - for (i = 0, sp += 2; i < lines && sp < buf + size; i++) { - if (i) - printf("\n "); - eol = memchr(sp, '\n', size - (sp - buf)); - len = eol ? eol - sp : size - (sp - buf); - fwrite(sp, len, 1, stdout); - if (!eol) - break; - sp = eol + 1; - } -free_return: - free(buf); -} - -static void ref_array_append(struct ref_array *array, const char *refname) -{ - size_t len = strlen(refname); - struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1); - memcpy(ref->refname, refname, len); - ref->refname[len] = '\0'; - REALLOC_ARRAY(array->items, array->nr + 1); - array->items[array->nr++] = ref; -} - -static int show_reference(const char *refname, const struct object_id *oid, - int flag, void *cb_data) -{ - struct ref_filter_cbdata *data = cb_data; - struct ref_array *array = data->array; - struct ref_filter *filter = data->filter; - - if (match_pattern(filter->name_patterns, refname)) { - if (filter->with_commit) { - struct commit *commit; - - commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) - return 0; - if (!contains(commit, filter->with_commit)) - return 0; - } - - if (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at)) - return 0; - - if (!filter->lines) { - if (tag_sort) - ref_array_append(array, refname); - else - printf("%s\n", refname); - return 0; - } - printf("%-15s ", refname); - show_tag_lines(oid, filter->lines); - putchar('\n'); - } - - return 0; -} - -static int sort_by_version(const void *a_, const void *b_) -{ - const struct ref_array_item *a = *((struct ref_array_item **)a_); - const struct ref_array_item *b = *((struct ref_array_item **)b_); - return versioncmp(a->refname, b->refname); -} - -static int list_tags(struct ref_filter *filter, int sort) +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting) { struct ref_array array; - struct ref_filter_cbdata data; + char *format, *to_free = NULL; + int i; memset(&array, 0, sizeof(array)); - data.array = &array; - data.filter = filter; if (filter->lines == -1) filter->lines = 0; - for_each_tag_ref(show_reference, &data); - if (sort) { - int i; - if ((sort & SORT_MASK) == VERCMP_SORT) - qsort(array.items, array.nr, - sizeof(struct ref_array_item *), sort_by_version); - if (sort & REVERSE_SORT) - for (i = array.nr - 1; i >= 0; i--) - printf("%s\n", array.items[i]->refname); - else - for (i = 0; i < array.nr; i++) - printf("%s\n", array.items[i]->refname); - ref_array_clear(&array); - } + if (filter->lines) { + to_free = xstrfmt("%s %%(contents:lines=%d)", + "%(align:15)%(refname:short)%(end)", filter->lines); + format = to_free; + } else + format = "%(refname:short)"; + + verify_ref_format(format); + filter_refs(&array, filter, FILTER_REFS_TAGS); + ref_array_sort(sorting, &array); + + for (i = 0; i < array.nr; i++) + show_ref_array_item(array.items[i], format, 0); + ref_array_clear(&array); + free(to_free); + return 0; } @@ -366,35 +123,26 @@ static const char tag_template_nocleanup[] = "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); -/* - * Parse a sort string, and return 0 if parsed successfully. Will return - * non-zero when the sort string does not parse into a known type. If var is - * given, the error message becomes a warning and includes information about - * the configuration value. - */ -static int parse_sort_string(const char *var, const char *arg, int *sort) +/* Parse arg given and add it the ref_sorting array */ +static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail) { - int type = 0, flags = 0; - - if (skip_prefix(arg, "-", &arg)) - flags |= REVERSE_SORT; + struct ref_sorting *s; + int len; - if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg)) - type = VERCMP_SORT; - else - type = STRCMP_SORT; + s = xcalloc(1, sizeof(*s)); + s->next = *sorting_tail; + *sorting_tail = s; - if (strcmp(arg, "refname")) { - if (!var) - return error(_("unsupported sort specification '%s'"), arg); - else { - warning(_("unsupported sort specification '%s' in variable '%s'"), - var, arg); - return -1; - } + if (*arg == '-') { + s->reverse = 1; + arg++; } + if (skip_prefix(arg, "version:", &arg) || + skip_prefix(arg, "v:", &arg)) + s->version = 1; - *sort = (type | flags); + len = strlen(arg); + s->atom = parse_ref_filter_atom(arg, arg+len); return 0; } @@ -402,11 +150,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort) static int git_tag_config(const char *var, const char *value, void *cb) { int status; + struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_sort_string(var, value, &tag_sort); + parse_sorting_string(value, sorting_tail); return 0; } @@ -564,13 +313,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) return check_refname_format(sb->buf, 0); } -static int parse_opt_sort(const struct option *opt, const char *arg, int unset) -{ - int *sort = opt->value; - - return parse_sort_string(NULL, arg, sort); -} - int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; @@ -587,6 +329,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; struct ref_filter filter; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), @@ -613,10 +356,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")), OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")), - { - OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), - PARSE_OPT_NONEG, parse_opt_sort - }, + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), { OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), 0, parse_opt_object_name @@ -624,7 +365,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_END() }; - git_config(git_tag_config, NULL); + git_config(git_tag_config, sorting_tail); memset(&opt, 0, sizeof(opt)); memset(&filter, 0, sizeof(filter)); @@ -650,6 +391,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("--column and -n are incompatible")); colopts = 0; } + if (!sorting) + sorting = ref_default_sorting(); if (cmdmode == 'l') { int ret; if (column_active(colopts)) { @@ -658,10 +401,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) copts.padding = 2; run_column_filter(colopts, &copts); } - if (filter.lines != -1 && tag_sort) - die(_("--sort and -n are incompatible")); filter.name_patterns = argv; - ret = list_tags(&filter, tag_sort); + ret = list_tags(&filter, sorting); if (column_active(colopts)) stop_column_filter(); return ret; diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index d31788cc6ce6a5..84153efb031737 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' ' test_expect_success 'invalid sort parameter in configuratoin' ' git config tag.sort "v:notvalid" && - git tag -l "foo*" >actual && - cat >expect <<-\EOF && - foo1.10 - foo1.3 - foo1.6 - EOF - test_cmp expect actual + test_must_fail git tag -l "foo*" ' test_expect_success 'version sort with prerelease reordering' ' From df0947417acd1058c17e2bf20374497f33084549 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Fri, 11 Sep 2015 20:36:46 +0530 Subject: [PATCH 100/539] tag.c: implement '--format' option Implement the '--format' option provided by 'ref-filter'. This lets the user list tags as per desired format similar to the implementation in 'git for-each-ref'. Add tests and documentation for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-tag.txt | 8 +++++++- builtin/tag.c | 25 +++++++++++++++---------- t/t7004-tag.sh | 12 ++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 3ac4a9672ccd42..0c7f4e65663c84 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -13,7 +13,8 @@ SYNOPSIS [ | ] 'git tag' -d ... 'git tag' [-n[]] -l [--contains ] [--points-at ] - [--column[=] | --no-column] [--create-reflog] [--sort=] [...] + [--column[=] | --no-column] [--create-reflog] [--sort=] + [--format=] [...] 'git tag' -v ... DESCRIPTION @@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines. The object that the new tag will refer to, usually a commit. Defaults to HEAD. +:: + A string that interpolates `%(fieldname)` from the object + pointed at by a ref being shown. The format is the same as + that of linkgit:git-for-each-ref[1]. When unspecified, + defaults to `%(refname:short)`. CONFIGURATION ------------- diff --git a/builtin/tag.c b/builtin/tag.c index 977a18c269613f..74eaf25d5cec12 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -23,17 +23,17 @@ static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u ] [-f] [-m | -F ] []"), N_("git tag -d ..."), N_("git tag -l [-n[]] [--contains ] [--points-at ]" - "\n\t\t[...]"), + "\n\t\t[--format=] [...]"), N_("git tag -v ..."), NULL }; static unsigned int colopts; -static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting) +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { struct ref_array array; - char *format, *to_free = NULL; + char *to_free = NULL; int i; memset(&array, 0, sizeof(array)); @@ -41,12 +41,15 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting) if (filter->lines == -1) filter->lines = 0; - if (filter->lines) { - to_free = xstrfmt("%s %%(contents:lines=%d)", - "%(align:15)%(refname:short)%(end)", filter->lines); - format = to_free; - } else - format = "%(refname:short)"; + if (!format) { + if (filter->lines) { + to_free = xstrfmt("%s %%(contents:lines=%d)", + "%(align:15)%(refname:short)%(end)", + filter->lines); + format = to_free; + } else + format = "%(refname:short)"; + } verify_ref_format(format); filter_refs(&array, filter, FILTER_REFS_TAGS); @@ -330,6 +333,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct strbuf err = STRBUF_INIT; struct ref_filter filter; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + const char *format = NULL; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), @@ -362,6 +366,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), 0, parse_opt_object_name }, + OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), OPT_END() }; @@ -402,7 +407,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) run_column_filter(colopts, &copts); } filter.name_patterns = argv; - ret = list_tags(&filter, sorting); + ret = list_tags(&filter, sorting, format); if (column_active(colopts)) stop_column_filter(); return ret; diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 84153efb031737..8987fb160df36d 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1519,4 +1519,16 @@ EOF" test_cmp expect actual ' +test_expect_success '--format should list tags as per format given' ' + cat >expect <<-\EOF && + refname : refs/tags/foo1.10 + refname : refs/tags/foo1.3 + refname : refs/tags/foo1.6 + refname : refs/tags/foo1.6-rc1 + refname : refs/tags/foo1.6-rc2 + EOF + git tag -l --format="refname : %(refname)" "foo*" >actual && + test_cmp expect actual +' + test_done From 5242860f548d1869ac2779726ad496f0ae8ab5ca Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 10 Sep 2015 21:52:49 +0530 Subject: [PATCH 101/539] tag.c: implement '--merged' and '--no-merged' options Use 'ref-filter' APIs to implement the '--merged' and '--no-merged' options into 'tag.c'. The '--merged' option lets the user to only list tags merged into the named commit. The '--no-merged' option lets the user to only list tags not merged into the named commit. If no object is provided it assumes HEAD as the object. Add documentation and tests for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-tag.txt | 7 ++++++- builtin/tag.c | 6 +++++- t/t7004-tag.sh | 27 +++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 0c7f4e65663c84..3803bf7fb97eec 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -14,7 +14,7 @@ SYNOPSIS 'git tag' -d ... 'git tag' [-n[]] -l [--contains ] [--points-at ] [--column[=] | --no-column] [--create-reflog] [--sort=] - [--format=] [...] + [--format=] [--[no-]merged []] [...] 'git tag' -v ... DESCRIPTION @@ -165,6 +165,11 @@ This option is only applicable when listing tags without annotation lines. that of linkgit:git-for-each-ref[1]. When unspecified, defaults to `%(refname:short)`. +--[no-]merged []:: + Only list tags whose tips are reachable, or not reachable + if '--no-merged' is used, from the specified commit ('HEAD' + if not specified). + CONFIGURATION ------------- By default, 'git tag' in sign-with-default mode (-s) will use your diff --git a/builtin/tag.c b/builtin/tag.c index 74eaf25d5cec12..b2e4ddca07ce7a 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -23,7 +23,7 @@ static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u ] [-f] [-m | -F ] []"), N_("git tag -d ..."), N_("git tag -l [-n[]] [--contains ] [--points-at ]" - "\n\t\t[--format=] [...]"), + "\n\t\t[--format=] [--[no-]merged []] [...]"), N_("git tag -v ..."), NULL }; @@ -360,6 +360,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")), OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")), + OPT_MERGED(&filter, N_("print only tags that are merged")), + OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), N_("field name to sort on"), &parse_opt_ref_sorting), { @@ -418,6 +420,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("--contains option is only allowed with -l.")); if (filter.points_at.nr) die(_("--points-at option is only allowed with -l.")); + if (filter.merge_commit) + die(_("--merged and --no-merged option are only allowed with -l")); if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag); if (cmdmode == 'v') diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 8987fb160df36d..3dd2f51e49d7e6 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1531,4 +1531,31 @@ test_expect_success '--format should list tags as per format given' ' test_cmp expect actual ' +test_expect_success 'setup --merged test tags' ' + git tag mergetest-1 HEAD~2 && + git tag mergetest-2 HEAD~1 && + git tag mergetest-3 HEAD +' + +test_expect_success '--merged cannot be used in non-list mode' ' + test_must_fail git tag --merged=mergetest-2 foo +' + +test_expect_success '--merged shows merged tags' ' + cat >expect <<-\EOF && + mergetest-1 + mergetest-2 + EOF + git tag -l --merged=mergetest-2 mergetest-* >actual && + test_cmp expect actual +' + +test_expect_success '--no-merged show unmerged tags' ' + cat >expect <<-\EOF && + mergetest-3 + EOF + git tag -l --no-merged=mergetest-2 mergetest-* >actual && + test_cmp expect actual +' + test_done From 96f78d39989d1fcf393d7bc42357467dd8cf0f15 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 15 Sep 2015 21:53:47 -0400 Subject: [PATCH 102/539] remote: add get-url subcommand Expanding `insteadOf` is a part of ls-remote --url and there is no way to expand `pushInsteadOf` as well. Add a get-url subcommand to be able to query both as well as a way to get all configured urls. Signed-off-by: Ben Boeckel Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 10 ++++++ builtin/remote.c | 59 ++++++++++++++++++++++++++++++++++++ t/t5505-remote.sh | 37 ++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 4c6d6de7b77c2f..3c9bf45829e084 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -15,6 +15,7 @@ SYNOPSIS 'git remote remove' 'git remote set-head' (-a | --auto | -d | --delete | ) 'git remote set-branches' [--add] ... +'git remote get-url' [--push] [--all] 'git remote set-url' [--push] [] 'git remote set-url --add' [--push] 'git remote set-url --delete' [--push] @@ -131,6 +132,15 @@ The named branches will be interpreted as if specified with the With `--add`, instead of replacing the list of currently tracked branches, adds to that list. +'get-url':: + +Retrieves the URLs for a remote. Configurations for `insteadOf` and +`pushInsteadOf` are expanded here. By default, only the first URL is listed. ++ +With '--push', push URLs are queried rather than fetch URLs. ++ +With '--all', all URLs for the remote will be listed. + 'set-url':: Changes URLs for the remote. Sets first URL for remote that matches diff --git a/builtin/remote.c b/builtin/remote.c index f4a6ec9f138bcf..ec9ec981a30e24 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -18,6 +18,7 @@ static const char * const builtin_remote_usage[] = { N_("git remote prune [-n | --dry-run] "), N_("git remote [-v | --verbose] update [-p | --prune] [( | )...]"), N_("git remote set-branches [--add] ..."), + N_("git remote get-url [--push] [--all] "), N_("git remote set-url [--push] []"), N_("git remote set-url --add "), N_("git remote set-url --delete "), @@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = { NULL }; +static const char * const builtin_remote_geturl_usage[] = { + N_("git remote get-url [--push] [--all] "), + NULL +}; + static const char * const builtin_remote_seturl_usage[] = { N_("git remote set-url [--push] []"), N_("git remote set-url --add "), @@ -1497,6 +1503,57 @@ static int set_branches(int argc, const char **argv) return set_remote_branches(argv[0], argv + 1, add_mode); } +static int get_url(int argc, const char **argv) +{ + int i, push_mode = 0, all_mode = 0; + const char *remotename = NULL; + struct remote *remote; + const char **url; + int url_nr; + struct option options[] = { + OPT_BOOL('\0', "push", &push_mode, + N_("query push URLs rather than fetch URLs")), + OPT_BOOL('\0', "all", &all_mode, + N_("return all URLs")), + OPT_END() + }; + argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0); + + if (argc != 1) + usage_with_options(builtin_remote_geturl_usage, options); + + remotename = argv[0]; + + if (!remote_is_configured(remotename)) + die(_("No such remote '%s'"), remotename); + remote = remote_get(remotename); + + url_nr = 0; + if (push_mode) { + url = remote->pushurl; + url_nr = remote->pushurl_nr; + } + /* else fetch mode */ + + /* Use the fetch URL when no push URLs were found or requested. */ + if (!url_nr) { + url = remote->url; + url_nr = remote->url_nr; + } + + if (!url_nr) + die(_("no URLs configured for remote '%s'"), remotename); + + if (all_mode) { + for (i = 0; i < url_nr; i++) + printf_ln("%s", url[i]); + } else { + printf_ln("%s", *url); + } + + return 0; +} + static int set_url(int argc, const char **argv) { int i, push_mode = 0, add_mode = 0, delete_mode = 0; @@ -1606,6 +1663,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = set_head(argc, argv); else if (!strcmp(argv[0], "set-branches")) result = set_branches(argc, argv); + else if (!strcmp(argv[0], "get-url")) + result = get_url(argc, argv); else if (!strcmp(argv[0], "set-url")) result = set_url(argc, argv); else if (!strcmp(argv[0], "show")) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 7a8499ce665c74..dfaf9d9f68939f 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -919,6 +919,19 @@ test_expect_success 'new remote' ' cmp expect actual ' +get_url_test () { + cat >expect && + git remote get-url "$@" >actual && + test_cmp expect actual +} + +test_expect_success 'get-url on new remote' ' + echo foo | get_url_test someremote && + echo foo | get_url_test --all someremote && + echo foo | get_url_test --push someremote && + echo foo | get_url_test --push --all someremote +' + test_expect_success 'remote set-url bar' ' git remote set-url someremote bar && echo bar >expect && @@ -961,6 +974,13 @@ test_expect_success 'remote set-url --push zot' ' cmp expect actual ' +test_expect_success 'get-url with different urls' ' + echo baz | get_url_test someremote && + echo baz | get_url_test --all someremote && + echo zot | get_url_test --push someremote && + echo zot | get_url_test --push --all someremote +' + test_expect_success 'remote set-url --push qux zot' ' git remote set-url --push someremote qux zot && echo qux >expect && @@ -995,6 +1015,14 @@ test_expect_success 'remote set-url --push --add aaa' ' cmp expect actual ' +test_expect_success 'get-url on multi push remote' ' + echo foo | get_url_test --push someremote && + get_url_test --push --all someremote <<-\EOF + foo + aaa + EOF +' + test_expect_success 'remote set-url --push bar aaa' ' git remote set-url --push someremote bar aaa && echo foo >expect && @@ -1039,6 +1067,14 @@ test_expect_success 'remote set-url --add bbb' ' cmp expect actual ' +test_expect_success 'get-url on multi fetch remote' ' + echo baz | get_url_test someremote && + get_url_test --all someremote <<-\EOF + baz + bbb + EOF +' + test_expect_success 'remote set-url --delete .*' ' test_must_fail git remote set-url --delete someremote .\* && echo "YYY" >expect && @@ -1108,6 +1144,7 @@ test_extra_arg rename origin newname test_extra_arg remove origin test_extra_arg set-head origin master # set-branches takes any number of args +test_extra_arg get-url origin newurl test_extra_arg set-url origin newurl oldurl # show takes any number of args # prune takes any number of args From 329e6e8794c347d3da92144f88ad838945508ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 19 Sep 2015 12:13:23 +0700 Subject: [PATCH 103/539] gc: save log from daemonized gc --auto and print it next time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While commit 9f673f9 (gc: config option for running --auto in background - 2014-02-08) helps reduce some complaints about 'gc --auto' hogging the terminal, it creates another set of problems. The latest in this set is, as the result of daemonizing, stderr is closed and all warnings are lost. This warning at the end of cmd_gc() is particularly important because it tells the user how to avoid "gc --auto" running repeatedly. Because stderr is closed, the user does not know, naturally they complain about 'gc --auto' wasting CPU. Daemonized gc now saves stderr to $GIT_DIR/gc.log. Following gc --auto will not run and gc.log printed out until the user removes gc.log. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/gc.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/builtin/gc.c b/builtin/gc.c index 005adbebea80a8..47fc1a6547f1ae 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -41,6 +41,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; static char *pidfile; +static struct lock_file log_lock; static void remove_pidfile(void) { @@ -55,6 +56,28 @@ static void remove_pidfile_on_signal(int signo) raise(signo); } +static void process_log_file(void) +{ + struct stat st; + if (!fstat(log_lock.fd, &st) && st.st_size) + commit_lock_file(&log_lock); + else + rollback_lock_file(&log_lock); +} + +static void process_log_file_at_exit(void) +{ + fflush(stderr); + process_log_file(); +} + +static void process_log_file_on_signal(int signo) +{ + process_log_file(); + sigchain_pop(signo); + raise(signo); +} + static void gc_config(void) { const char *value; @@ -248,6 +271,24 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) return NULL; } +static int report_last_gc_error(void) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = strbuf_read_file(&sb, git_path("gc.log"), 0); + if (ret > 0) + return error(_("The last gc run reported the following. " + "Please correct the root cause\n" + "and remove %s.\n" + "Automatic cleanup will not be performed " + "until the file is removed.\n\n" + "%s"), + git_path("gc.log"), sb.buf); + strbuf_release(&sb); + return 0; +} + static int gc_before_repack(void) { if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) @@ -269,6 +310,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) int force = 0; const char *name; pid_t pid; + int daemonized = 0; struct option builtin_gc_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), @@ -324,13 +366,16 @@ int cmd_gc(int argc, const char **argv, const char *prefix) fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n")); } if (detach_auto) { + if (report_last_gc_error()) + return -1; + if (gc_before_repack()) return -1; /* * failure to daemonize is ok, we'll continue * in foreground */ - daemonize(); + daemonized = !daemonize(); } } else add_repack_all_option(); @@ -343,6 +388,15 @@ int cmd_gc(int argc, const char **argv, const char *prefix) name, (uintmax_t)pid); } + if (daemonized) { + hold_lock_file_for_update(&log_lock, + git_path("gc.log"), + LOCK_DIE_ON_ERROR); + dup2(log_lock.fd, 2); + sigchain_push_common(process_log_file_on_signal); + atexit(process_log_file_at_exit); + } + if (gc_before_repack()) return -1; From 340f2c5e631c788c0e4a508c21349e04ceed7966 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Sat, 19 Sep 2015 09:47:48 +0200 Subject: [PATCH 104/539] Documentation: use 'keyid' consistently, not 'key-id' Signed-off-by: Matthieu Moy Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-cherry-pick.txt | 6 +++--- Documentation/git-commit.txt | 2 +- Documentation/git-merge.txt | 2 +- Documentation/git-revert.txt | 6 +++--- Documentation/git-tag.txt | 18 +++++++++--------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 1147c71da605c1..66ab29742c393b 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] - [-S[]] ... + [-S[]] ... 'git cherry-pick' --continue 'git cherry-pick' --quit 'git cherry-pick' --abort @@ -101,8 +101,8 @@ effect to your index in a row. --signoff:: Add Signed-off-by line at the end of the commit message. --S[]:: ---gpg-sign[=]:: +-S[]:: +--gpg-sign[=]:: GPG-sign commits. --ff:: diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 904dafa0f7070f..51c63d66761d76 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -13,7 +13,7 @@ SYNOPSIS [-F | -m ] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=] [--date=] [--cleanup=] [--[no-]status] - [-i | -o] [-S[]] [--] [...] + [-i | -o] [-S[]] [--] [...] DESCRIPTION ----------- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 273a1009be002e..61d43a712d21ef 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit] - [-s ] [-X ] [-S[]] + [-s ] [-X ] [-S[]] [--[no-]rerere-autoupdate] [-m ] [...] 'git merge' HEAD ... 'git merge' --abort diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index cceb5f2f7fa0c4..9eb83f01a4513a 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -8,7 +8,7 @@ git-revert - Revert some existing commits SYNOPSIS -------- [verse] -'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[]] ... +'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[]] ... 'git revert' --continue 'git revert' --quit 'git revert' --abort @@ -80,8 +80,8 @@ more details. This is useful when reverting more than one commits' effect to your index in a row. --S[]:: ---gpg-sign[=]:: +-S[]:: +--gpg-sign[=]:: GPG-sign commits. -s:: diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 4b04c2b7d50052..e95a3364bb6038 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -9,7 +9,7 @@ git-tag - Create, list, delete or verify a tag object signed with GPG SYNOPSIS -------- [verse] -'git tag' [-a | -s | -u ] [-f] [-m | -F ] +'git tag' [-a | -s | -u ] [-f] [-m | -F ] [ | ] 'git tag' -d ... 'git tag' [-n[]] -l [--contains ] [--points-at ] @@ -24,19 +24,19 @@ to delete, list or verify tags. Unless `-f` is given, the named tag must not yet exist. -If one of `-a`, `-s`, or `-u ` is passed, the command +If one of `-a`, `-s`, or `-u ` is passed, the command creates a 'tag' object, and requires a tag message. Unless `-m ` or `-F ` is given, an editor is started for the user to type in the tag message. -If `-m ` or `-F ` is given and `-a`, `-s`, and `-u ` +If `-m ` or `-F ` is given and `-a`, `-s`, and `-u ` are absent, `-a` is implied. Otherwise just a tag reference for the SHA-1 object name of the commit object is created (i.e. a lightweight tag). A GnuPG signed tag object will be created when `-s` or `-u -` is used. When `-u ` is not used, the +` is used. When `-u ` is not used, the committer identity for the current user is used to find the GnuPG key for signing. The configuration variable `gpg.program` is used to specify custom GnuPG binary. @@ -63,8 +63,8 @@ OPTIONS --sign:: Make a GPG-signed tag, using the default e-mail address's key. --u :: ---local-user=:: +-u :: +--local-user=:: Make a GPG-signed tag, using the given key. -f:: @@ -125,14 +125,14 @@ This option is only applicable when listing tags without annotation lines. Use the given tag message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. - Implies `-a` if none of `-a`, `-s`, or `-u ` + Implies `-a` if none of `-a`, `-s`, or `-u ` is given. -F :: --file=:: Take the tag message from the given file. Use '-' to read the message from the standard input. - Implies `-a` if none of `-a`, `-s`, or `-u ` + Implies `-a` if none of `-a`, `-s`, or `-u ` is given. --cleanup=:: @@ -163,7 +163,7 @@ it in the repository configuration as follows: ------------------------------------- [user] - signingKey = + signingKey = ------------------------------------- From 318ca61531d3189aef7b32a924518db1dd9f3374 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Sat, 19 Sep 2015 09:47:49 +0200 Subject: [PATCH 105/539] Documentation/grep: fix documentation of -O Since the argument of -O, --open-file-in-pager is optional, it must be stuck to the command. Reflect this in the documentation. Signed-off-by: Matthieu Moy Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-grep.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 31811f16bdaac4..1c07c7fe9ac8fe 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -160,8 +160,8 @@ OPTIONS For better compatibility with 'git diff', `--name-only` is a synonym for `--files-with-matches`. --O []:: ---open-files-in-pager []:: +-O[]:: +--open-files-in-pager[=]:: Open the matching files in the pager (not the output of 'grep'). If the pager happens to be "less" or "vi", and the user specified only one pattern, the first file is positioned at From 2b594bf90d84fd96f2b1fe904c1e62e391ceb4a0 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Sat, 19 Sep 2015 09:47:50 +0200 Subject: [PATCH 106/539] Documentation: explain optional arguments better Improve the documentation of commands taking optional arguments in two ways: * Documents the behavior of '-O' (for grep) and '-S' (for commands creating commits) when used without the optional argument. * Document the syntax of these options. For the second point, the behavior is documented in gitcli(7), but it is easy for users to miss, and hard for the same user to understand why e.g. "git status -u no" does not work. Document this explicitly in the documentation of each short option having an optional argument: they are the most error prone since there is no '=' sign between the option and its argument. Signed-off-by: Matthieu Moy Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-am.txt | 4 +++- Documentation/git-cherry-pick.txt | 4 +++- Documentation/git-commit-tree.txt | 4 +++- Documentation/git-commit.txt | 4 +++- Documentation/git-grep.txt | 5 ++++- Documentation/git-merge.txt | 4 +++- Documentation/git-rebase.txt | 4 +++- Documentation/git-revert.txt | 4 +++- Documentation/git-status.txt | 5 +++-- 9 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 0d8ba48f792ae8..6b09891db33d55 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -138,7 +138,9 @@ default. You can use `--no-utf8` to override this. -S[]:: --gpg-sign[=]:: - GPG-sign commits. + GPG-sign commits. The `keyid` argument is optional and + defaults to the committer identity; if specified, it must be + stuck to the option without a space. --continue:: -r:: diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 66ab29742c393b..77da29a474518c 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -103,7 +103,9 @@ effect to your index in a row. -S[]:: --gpg-sign[=]:: - GPG-sign commits. + GPG-sign commits. The `keyid` argument is optional and + defaults to the committer identity; if specified, it must be + stuck to the option without a space. --ff:: If the current HEAD is the same as the parent of the diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index f5f2a8d3265027..a0b5457304008c 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -56,7 +56,9 @@ OPTIONS -S[]:: --gpg-sign[=]:: - GPG-sign commit. + GPG-sign commits. The `keyid` argument is optional and + defaults to the committer identity; if specified, it must be + stuck to the option without a space. --no-gpg-sign:: Countermand `commit.gpgSign` configuration variable that is diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 51c63d66761d76..7f34a5b33103ed 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -314,7 +314,9 @@ changes to tracked files. -S[]:: --gpg-sign[=]:: - GPG-sign commit. + GPG-sign commits. The `keyid` argument is optional and + defaults to the committer identity; if specified, it must be + stuck to the option without a space. --no-gpg-sign:: Countermand `commit.gpgSign` configuration variable that is diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 1c07c7fe9ac8fe..4a44d6da13cb74 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -165,7 +165,10 @@ OPTIONS Open the matching files in the pager (not the output of 'grep'). If the pager happens to be "less" or "vi", and the user specified only one pattern, the first file is positioned at - the first match automatically. + the first match automatically. The `pager` argument is + optional; if specified, it must be stuck to the option + without a space. If `pager` is unspecified, the default pager + will be used (see `core.pager` in linkgit:git-config[1]). -z:: --null:: diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 61d43a712d21ef..c06c4f2fb022f2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -67,7 +67,9 @@ include::merge-options.txt[] -S[]:: --gpg-sign[=]:: - GPG-sign the resulting merge commit. + GPG-sign the resulting merge commit. The `keyid` argument is + optional and defaults to the committer identity; if specified, + it must be stuck to the option without a space. -m :: Set the commit message to be used for the merge commit (in diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 1d01baa5fcfd03..6d729458140bde 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -285,7 +285,9 @@ which makes little sense. -S[]:: --gpg-sign[=]:: - GPG-sign commits. + GPG-sign commits. The `keyid` argument is optional and + defaults to the committer identity; if specified, it must be + stuck to the option without a space. -q:: --quiet:: diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 9eb83f01a4513a..b15139ffdcda48 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -82,7 +82,9 @@ effect to your index in a row. -S[]:: --gpg-sign[=]:: - GPG-sign commits. + GPG-sign commits. The `keyid` argument is optional and + defaults to the committer identity; if specified, it must be + stuck to the option without a space. -s:: --signoff:: diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 335f3123353482..e1e8f57cdd217b 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -53,8 +53,9 @@ OPTIONS --untracked-files[=]:: Show untracked files. + -The mode parameter is optional (defaults to 'all'), and is used to -specify the handling of untracked files. +The mode parameter is used to specify the handling of untracked files. +It is optional: it defaults to 'all', and if specified, it must be +stuck to the option (e.g. `-uno`, but not `-u no`). + The possible options are: + From e6efecc46a34a984535e6a90e45a9db45af4eff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 21 Sep 2015 16:56:14 +0700 Subject: [PATCH 107/539] dir.c: make last_exclude_matching_from_list() run til the end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The next patch adds some post processing to the result value before it's returned to the caller. Keep all branches reach the end of the function, so we can do it all in one place. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dir.c b/dir.c index 0943a81964ddb7..4893181a0c5774 100644 --- a/dir.c +++ b/dir.c @@ -752,6 +752,7 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, int *dtype, struct exclude_list *el) { + struct exclude *exc = NULL; /* undecided */ int i; if (!el->nr) @@ -773,18 +774,22 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, if (match_basename(basename, pathlen - (basename - pathname), exclude, prefix, x->patternlen, - x->flags)) - return x; + x->flags)) { + exc = x; + break; + } continue; } assert(x->baselen == 0 || x->base[x->baselen - 1] == '/'); if (match_pathname(pathname, pathlen, x->base, x->baselen ? x->baselen - 1 : 0, - exclude, prefix, x->patternlen, x->flags)) - return x; + exclude, prefix, x->patternlen, x->flags)) { + exc = x; + break; + } } - return NULL; /* undecided */ + return exc; } /* From 57534ee77d22e725d971ee89c77dc6aad61c573f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 21 Sep 2015 16:56:15 +0700 Subject: [PATCH 108/539] dir.c: don't exclude whole dir prematurely if neg pattern may match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If there is a pattern "!foo/bar", this patch makes it not exclude "foo" right away. This gives us a chance to examine "foo" and re-include "foo/bar". In order for it to detect that the directory under examination should not be excluded right away, in other words it is a parent directory of a negative pattern, the "directory path" of the negative pattern must be literal. Patterns like "!f?o/bar" can't stop "foo" from being excluded. Basename matching (i.e. "no slashes in the pattern") or must-be-dir matching (i.e. "trailing slash in the pattern") does not work well with this. For example, if we descend in "foo" and are examining "foo/abc", current code for "foo/" pattern will check if path "foo/abc", not "foo", is a directory. The same problem with basename matching. These may need big code reorg to make it work. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/gitignore.txt | 23 ++++++++-- dir.c | 74 +++++++++++++++++++++++++++++- t/t3001-ls-files-others-exclude.sh | 25 ++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 4fd04423e9342e..9cce703c5a0aab 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -82,12 +82,12 @@ PATTERN FORMAT - An optional prefix "`!`" which negates the pattern; any matching file excluded by a previous pattern will become - included again. It is not possible to re-include a file if a parent - directory of that file is excluded. Git doesn't list excluded - directories for performance reasons, so any patterns on contained - files have no effect, no matter where they are defined. + included again. Put a backslash ("`\`") in front of the first "`!`" for patterns that begin with a literal "`!`", for example, "`\!important!.txt`". + It is possible to re-include a file if a parent directory of that + file is excluded if certain conditions are met. See section NOTES + for detail. - If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find @@ -141,6 +141,21 @@ not tracked by Git remain untracked. To stop tracking a file that is currently tracked, use 'git rm --cached'. +To re-include files or directories when their parent directory is +excluded, the following conditions must be met: + + - The rules to exclude a directory and re-include a subset back must + be in the same .gitignore file. + + - The directory part in the re-include rules must be literal (i.e. no + wildcards) + + - The rules to exclude the parent directory must not end with a + trailing slash. + + - The rules to exclude the parent directory must have at least one + slash. + EXAMPLES -------- diff --git a/dir.c b/dir.c index 4893181a0c5774..894027c0cd7e6a 100644 --- a/dir.c +++ b/dir.c @@ -733,6 +733,25 @@ int match_pathname(const char *pathname, int pathlen, */ if (!patternlen && !namelen) return 1; + /* + * This can happen when we ignore some exclude rules + * on directories in other to see if negative rules + * may match. E.g. + * + * /abc + * !/abc/def/ghi + * + * The pattern of interest is "/abc". On the first + * try, we should match path "abc" with this pattern + * in the "if" statement right above, but the caller + * ignores it. + * + * On the second try with paths within "abc", + * e.g. "abc/xyz", we come here and try to match it + * with "/abc". + */ + if (!patternlen && namelen && *name == '/') + return 1; } return fnmatch_icase_mem(pattern, patternlen, @@ -740,6 +759,48 @@ int match_pathname(const char *pathname, int pathlen, WM_PATHNAME) == 0; } +/* + * Return non-zero if pathname is a directory and an ancestor of the + * literal path in a (negative) pattern. This is used to keep + * descending in "foo" and "foo/bar" when the pattern is + * "!foo/bar/.gitignore". "foo/notbar" will not be descended however. + */ +static int match_neg_path(const char *pathname, int pathlen, int *dtype, + const char *base, int baselen, + const char *pattern, int prefix, int patternlen, + int flags) +{ + assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR)); + + if (*dtype == DT_UNKNOWN) + *dtype = get_dtype(NULL, pathname, pathlen); + if (*dtype != DT_DIR) + return 0; + + if (*pattern == '/') { + pattern++; + patternlen--; + prefix--; + } + + if (baselen) { + if (((pathlen < baselen && base[pathlen] == '/') || + pathlen == baselen) && + !strncmp_icase(pathname, base, pathlen)) + return 1; + pathname += baselen + 1; + pathlen -= baselen + 1; + } + + + if (prefix && + ((pathlen < prefix && pattern[pathlen] == '/') && + !strncmp_icase(pathname, pattern, pathlen))) + return 1; + + return 0; +} + /* * Scan the given exclude list in reverse to see whether pathname * should be ignored. The first match (i.e. the last on the list), if @@ -753,7 +814,7 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, struct exclude_list *el) { struct exclude *exc = NULL; /* undecided */ - int i; + int i, matched_negative_path = 0; if (!el->nr) return NULL; /* undefined */ @@ -788,7 +849,18 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, exc = x; break; } + + if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path && + match_neg_path(pathname, pathlen, dtype, x->base, + x->baselen ? x->baselen - 1 : 0, + exclude, prefix, x->patternlen, x->flags)) + matched_negative_path = 1; } + if (exc && + !(exc->flags & EXC_FLAG_NEGATIVE) && + !(exc->flags & EXC_FLAG_NODIR) && + matched_negative_path) + exc = NULL; return exc; } diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index b2798feef7316e..2a13af17585d6a 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -305,4 +305,29 @@ test_expect_success 'ls-files with "**" patterns and no slashes' ' test_cmp expect actual ' +test_expect_success 'negative patterns' ' + git init reinclude && + ( + cd reinclude && + cat >.gitignore <<-\EOF && + /fooo + /foo + !foo/bar/bar + EOF + mkdir fooo && + cat >fooo/.gitignore <<-\EOF && + !/* + EOF + mkdir -p foo/bar && + touch abc foo/def foo/bar/ghi foo/bar/bar && + git ls-files -o --exclude-standard >../actual && + cat >../expected <<-\EOF && + .gitignore + abc + foo/bar/bar + EOF + test_cmp ../expected ../actual + ) +' + test_done From 4cb870d8042bef3fdd953c633463eda24ce78f3d Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Mon, 21 Sep 2015 10:49:18 +0200 Subject: [PATCH 109/539] git-p4: use replacement character for non UTF-8 characters in paths If non UTF-8 characters are detected in paths then replace them with a placeholder instead of throwing a UnicodeDecodeError exception. This restores the original (implicit) implementation that was broken in 00a9403. Signed-off-by: Lars Schneider Reviewed-by: Luke Diamand Signed-off-by: Junio C Hamano --- git-p4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-p4.py b/git-p4.py index 65feb22f58f89e..603045050c95ca 100755 --- a/git-p4.py +++ b/git-p4.py @@ -2219,7 +2219,7 @@ def streamOneP4File(self, file, contents): encoding = 'utf8' if gitConfig('git-p4.pathEncoding'): encoding = gitConfig('git-p4.pathEncoding') - relPath = relPath.decode(encoding).encode('utf8', 'replace') + relPath = relPath.decode(encoding, 'replace').encode('utf8', 'replace') if self.verbose: print 'Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, relPath) From 6a9d16a0a80ef53c1fcc0f6bc3d1d24d21961272 Mon Sep 17 00:00:00 2001 From: Gabor Bernat Date: Mon, 7 Sep 2015 15:52:08 +0200 Subject: [PATCH 110/539] filter-branch: add passed/remaining seconds on progress adds seconds progress and estimated seconds time if getting the current timestamp is supported by the date +%s command Signed-off-by: Gabor Bernat Signed-off-by: Junio C Hamano --- git-filter-branch.sh | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 5b3f63d8bbc65e..b7bad160061177 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -275,11 +275,39 @@ commits=$(wc -l <../revs | tr -d " ") test $commits -eq 0 && die "Found nothing to rewrite" # Rewrite the commits +report_progress () +{ + if test -n "$progress" && + test $git_filter_branch__commit_count -gt $next_sample_at + then + now_timestamp=$(date +%s) + elapsed_seconds=$(($now_timestamp - $start_timestamp)) + remaining_second=$(( ($commits - $git_filter_branch__commit_count) * $elapsed_seconds / $git_filter_branch__commit_count )) + if test $elapsed_seconds -gt 0 + then + next_sample_at=$(( ($elapsed_seconds + 1) * $git_filter_branch__commit_count / $elapsed_seconds )) + else + next_sample_at=$(($next_sample_at + 1)) + fi + progress=" ($elapsed_seconds seconds passed, remaining $remaining_second predicted)" + fi + printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)$progress " +} git_filter_branch__commit_count=0 + +progress= start_timestamp= +if date '+%s' 2>/dev/null | grep -q '^[0-9][0-9]*$' +then + next_sample_at=0 + progress="dummy to ensure this is not empty" + start_timestamp=$(date '+%s') +fi + while read commit parents; do git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1)) - printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)" + + report_progress case "$filter_subdir" in "") From 71400d97b12abeba25f2e8245a9751daf8583142 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 21 Sep 2015 15:16:20 -0700 Subject: [PATCH 111/539] filter-branch: make report-progress more readable The name of some variables that are used very locally in this function were overly long; they were making the lines harder to read and the longer names didn't add much more information. Signed-off-by: Junio C Hamano --- git-filter-branch.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index b7bad160061177..5777947a5d6c0f 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -280,18 +280,20 @@ report_progress () if test -n "$progress" && test $git_filter_branch__commit_count -gt $next_sample_at then - now_timestamp=$(date +%s) - elapsed_seconds=$(($now_timestamp - $start_timestamp)) - remaining_second=$(( ($commits - $git_filter_branch__commit_count) * $elapsed_seconds / $git_filter_branch__commit_count )) - if test $elapsed_seconds -gt 0 + count=$git_filter_branch__commit_count + + now=$(date +%s) + elapsed=$(($now - $start_timestamp)) + remaining=$(( ($commits - $count) * $elapsed / $count )) + if test $elapsed -gt 0 then - next_sample_at=$(( ($elapsed_seconds + 1) * $git_filter_branch__commit_count / $elapsed_seconds )) + next_sample_at=$(( ($elapsed + 1) * $count / $elapsed )) else next_sample_at=$(($next_sample_at + 1)) fi - progress=" ($elapsed_seconds seconds passed, remaining $remaining_second predicted)" + progress=" ($elapsed seconds passed, remaining $remaining predicted)" fi - printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)$progress " + printf "\rRewrite $commit ($count/$commits)$progress " } git_filter_branch__commit_count=0 From fe18a0f2793b7559d5565bc65b53c7c018de3c21 Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Mon, 21 Sep 2015 12:01:40 +0200 Subject: [PATCH 112/539] git-p4: add test case for "Translation of file content failed" error A P4 repository can get into a state where it contains a file with type UTF-16 that does not contain a valid UTF-16 BOM. If git-p4 attempts to retrieve the file then the process crashes with a "Translation of file content failed" error. More info here: http://answers.perforce.com/articles/KB/3117 Signed-off-by: Lars Schneider Signed-off-by: Junio C Hamano --- t/t9825-git-p4-handle-utf16-without-bom.sh | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 t/t9825-git-p4-handle-utf16-without-bom.sh diff --git a/t/t9825-git-p4-handle-utf16-without-bom.sh b/t/t9825-git-p4-handle-utf16-without-bom.sh new file mode 100755 index 00000000000000..bdd59111027628 --- /dev/null +++ b/t/t9825-git-p4-handle-utf16-without-bom.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='git p4 handling of UTF-16 files without BOM' + +. ./lib-git-p4.sh + +UTF16="\227\000\227\000" + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot with UTF-16 encoded file and artificially remove BOM' ' + ( + cd "$cli" && + printf "$UTF16" >file1 && + p4 add -t utf16 file1 && + p4 submit -d "file1" + ) && + + ( + cd db && + p4d -jc && + # P4D automatically adds a BOM. Remove it here to make the file invalid. + sed -e "\$d" depot/file1,v >depot/file1,v.new && + mv depot/file1,v.new depot/file1,v && + printf "@$UTF16@" >>depot/file1,v && + p4d -jrF checkpoint.1 + ) +' + +test_expect_failure 'clone depot with invalid UTF-16 file in verbose mode' ' + git p4 clone --dest="$git" --verbose //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + printf "$UTF16" >expect && + test_cmp_bin expect file1 + ) +' + +test_expect_failure 'clone depot with invalid UTF-16 file in non-verbose mode' ' + git p4 clone --dest="$git" //depot +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done From 1f5f3907117021961545e42097829001b58288db Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Mon, 21 Sep 2015 12:01:41 +0200 Subject: [PATCH 113/539] git-p4: handle "Translation of file content failed" A P4 repository can get into a state where it contains a file with type UTF-16 that does not contain a valid UTF-16 BOM. If git-p4 attempts to retrieve the file then the process crashes with a "Translation of file content failed" error. More info here: http://answers.perforce.com/articles/KB/3117 Fix this by detecting this error and retrieving the file as binary instead. The result in Git is the same. Known issue: This works only if git-p4 is executed in verbose mode. In normal mode no exceptions are thrown and git-p4 just exits. Signed-off-by: Lars Schneider Signed-off-by: Junio C Hamano --- git-p4.py | 27 +++++++++++++--------- t/t9825-git-p4-handle-utf16-without-bom.sh | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/git-p4.py b/git-p4.py index 0093fa3d8391b4..3c323510004a75 100755 --- a/git-p4.py +++ b/git-p4.py @@ -134,13 +134,11 @@ def read_pipe(c, ignore_error=False): sys.stderr.write('Reading pipe: %s\n' % str(c)) expand = isinstance(c,basestring) - p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand) - pipe = p.stdout - val = pipe.read() - if p.wait() and not ignore_error: - die('Command failed: %s' % str(c)) - - return val + p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand) + (out, err) = p.communicate() + if p.returncode != 0 and not ignore_error: + die('Command failed: %s\nError: %s' % (str(c), err)) + return out def p4_read_pipe(c, ignore_error=False): real_cmd = p4_build_cmd(c) @@ -2193,10 +2191,17 @@ def streamOneP4File(self, file, contents): # them back too. This is not needed to the cygwin windows version, # just the native "NT" type. # - text = p4_read_pipe(['print', '-q', '-o', '-', "%s@%s" % (file['depotFile'], file['change']) ]) - if p4_version_string().find("/NT") >= 0: - text = text.replace("\r\n", "\n") - contents = [ text ] + try: + text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])]) + except Exception as e: + if 'Translation of file content failed' in str(e): + type_base = 'binary' + else: + raise e + else: + if p4_version_string().find('/NT') >= 0: + text = text.replace('\r\n', '\n') + contents = [ text ] if type_base == "apple": # Apple filetype files will be streamed as a concatenation of diff --git a/t/t9825-git-p4-handle-utf16-without-bom.sh b/t/t9825-git-p4-handle-utf16-without-bom.sh index bdd59111027628..1551845dc178e3 100755 --- a/t/t9825-git-p4-handle-utf16-without-bom.sh +++ b/t/t9825-git-p4-handle-utf16-without-bom.sh @@ -29,7 +29,7 @@ test_expect_success 'init depot with UTF-16 encoded file and artificially remove ) ' -test_expect_failure 'clone depot with invalid UTF-16 file in verbose mode' ' +test_expect_success 'clone depot with invalid UTF-16 file in verbose mode' ' git p4 clone --dest="$git" --verbose //depot && test_when_finished cleanup_git && ( From e14c92e841fd6d99c8c17af05a411a85ac212254 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Tue, 22 Sep 2015 15:15:03 -0700 Subject: [PATCH 114/539] notes: correct documentation of DWIMery for notes references expand_notes_ref is used by --ref from git-notes(1) and --notes from the git log to find the full refname of a notes reference. Previously the documentation of these options was not clear about what sorts of expansions would be performed. Fix the documentation to clearly and accurately describe the behavior of the expansions. Add a test for this expansion when using git notes get-ref in order to prevent future patches from changing this behavior. Signed-off-by: Jacob Keller Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 4 +++- Documentation/pretty-options.txt | 5 +++-- t/t3301-notes.sh | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 851518d531b539..b8889ecf1c963b 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -162,7 +162,9 @@ OPTIONS --ref :: Manipulate the notes tree in . This overrides 'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref - is taken to be in `refs/notes/` if it is not qualified. + specifies the full refname when it begins with `refs/notes/`; when it + begins with `notes/`, `refs/` and otherwise `refs/notes/` is prefixed + to form a full name of the ref. --ignore-missing:: Do not consider it an error to request removing notes from an diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 8d6c5cec4c5edc..4b659ac1a6a6c1 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -55,8 +55,9 @@ By default, the notes shown are from the notes refs listed in the environment overrides). See linkgit:git-config[1] for more details. + With an optional '' argument, show this notes ref instead of the -default notes ref(s). The ref is taken to be in `refs/notes/` if it -is not qualified. +default notes ref(s). The ref specifies the full refname when it begins +with `refs/notes/`; when it begins with `notes/`, `refs/` and otherwise +`refs/notes/` is prefixed to form a full name of the ref. + Multiple --notes options can be combined to control which notes are being displayed. Examples: "--notes=foo" will show only notes from diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 8cffd35fb03d7c..cd70274ea51ac5 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -1122,6 +1122,12 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' ' test_must_fail git notes copy one two three ' +test_expect_success 'git notes get-ref expands refs/heads/master to refs/notes/refs/heads/master' ' + test_unconfig core.notesRef && + sane_unset GIT_NOTES_REF && + test "$(git notes --ref=refs/heads/master get-ref)" = "refs/notes/refs/heads/master" +' + test_expect_success 'git notes get-ref (no overrides)' ' test_unconfig core.notesRef && sane_unset GIT_NOTES_REF && From a5adaced2e13c135d5d9cc65be9eb95aa3bacedf Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 16 Sep 2015 13:12:52 -0400 Subject: [PATCH 115/539] transport: add a protocol-whitelist environment variable If we are cloning an untrusted remote repository into a sandbox, we may also want to fetch remote submodules in order to get the complete view as intended by the other side. However, that opens us up to attacks where a malicious user gets us to clone something they would not otherwise have access to (this is not necessarily a problem by itself, but we may then act on the cloned contents in a way that exposes them to the attacker). Ideally such a setup would sandbox git entirely away from high-value items, but this is not always practical or easy to set up (e.g., OS network controls may block multiple protocols, and we would want to enable some but not others). We can help this case by providing a way to restrict particular protocols. We use a whitelist in the environment. This is more annoying to set up than a blacklist, but defaults to safety if the set of protocols git supports grows). If no whitelist is specified, we continue to default to allowing all protocols (this is an "unsafe" default, but since the minority of users will want this sandboxing effect, it is the only sensible one). A note on the tests: ideally these would all be in a single test file, but the git-daemon and httpd test infrastructure is an all-or-nothing proposition rather than a test-by-test prerequisite. By putting them all together, we would be unable to test the file-local code on machines without apache. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git.txt | 32 ++++++++++++ connect.c | 5 ++ t/lib-proto-disable.sh | 96 ++++++++++++++++++++++++++++++++++ t/t5810-proto-disable-local.sh | 14 +++++ t/t5811-proto-disable-git.sh | 20 +++++++ t/t5812-proto-disable-http.sh | 20 +++++++ t/t5813-proto-disable-ssh.sh | 20 +++++++ t/t5814-proto-disable-ext.sh | 18 +++++++ transport-helper.c | 2 + transport.c | 21 +++++++- transport.h | 7 +++ 11 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 t/lib-proto-disable.sh create mode 100755 t/t5810-proto-disable-local.sh create mode 100755 t/t5811-proto-disable-git.sh create mode 100755 t/t5812-proto-disable-http.sh create mode 100755 t/t5813-proto-disable-ssh.sh create mode 100755 t/t5814-proto-disable-ext.sh diff --git a/Documentation/git.txt b/Documentation/git.txt index a62ed6f11abf97..b6a12b32ee5b7b 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -1045,6 +1045,38 @@ GIT_ICASE_PATHSPECS:: an operation has touched every ref (e.g., because you are cloning a repository to make a backup). +`GIT_ALLOW_PROTOCOL`:: + If set, provide a colon-separated list of protocols which are + allowed to be used with fetch/push/clone. This is useful to + restrict recursive submodule initialization from an untrusted + repository. Any protocol not mentioned will be disallowed (i.e., + this is a whitelist, not a blacklist). If the variable is not + set at all, all protocols are enabled. The protocol names + currently used by git are: + + - `file`: any local file-based path (including `file://` URLs, + or local paths) + + - `git`: the anonymous git protocol over a direct TCP + connection (or proxy, if configured) + + - `ssh`: git over ssh (including `host:path` syntax, + `git+ssh://`, etc). + + - `rsync`: git over rsync + + - `http`: git over http, both "smart http" and "dumb http". + Note that this does _not_ include `https`; if you want both, + you should specify both as `http:https`. + + - any external helpers are named by their protocol (e.g., use + `hg` to allow the `git-remote-hg` helper) ++ +Note that this controls only git's internal protocol selection. +If libcurl is used (e.g., by the `http` transport), it may +redirect to other protocols. There is not currently any way to +restrict this. + Discussion[[Discussion]] ------------------------ diff --git a/connect.c b/connect.c index 14c924b030cc17..bd4b50ea153c06 100644 --- a/connect.c +++ b/connect.c @@ -9,6 +9,7 @@ #include "url.h" #include "string-list.h" #include "sha1-array.h" +#include "transport.h" static char *server_capabilities; static const char *parse_feature_value(const char *, const char *, int *); @@ -694,6 +695,8 @@ struct child_process *git_connect(int fd[2], const char *url, else target_host = xstrdup(hostandport); + transport_check_allowed("git"); + /* These underlying connection commands die() if they * cannot connect. */ @@ -727,6 +730,7 @@ struct child_process *git_connect(int fd[2], const char *url, int putty; char *ssh_host = hostandport; const char *port = NULL; + transport_check_allowed("ssh"); get_host_and_port(&ssh_host, &port); if (!port) @@ -768,6 +772,7 @@ struct child_process *git_connect(int fd[2], const char *url, /* remove repo-local variables from the environment */ conn->env = local_repo_env; conn->use_shell = 1; + transport_check_allowed("file"); } argv_array_push(&conn->args, cmd.buf); diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh new file mode 100644 index 00000000000000..b0917d93e64a93 --- /dev/null +++ b/t/lib-proto-disable.sh @@ -0,0 +1,96 @@ +# Test routines for checking protocol disabling. + +# test cloning a particular protocol +# $1 - description of the protocol +# $2 - machine-readable name of the protocol +# $3 - the URL to try cloning +test_proto () { + desc=$1 + proto=$2 + url=$3 + + test_expect_success "clone $1 (enabled)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=$proto && + export GIT_ALLOW_PROTOCOL && + git clone --bare "$url" tmp.git + ) + ' + + test_expect_success "fetch $1 (enabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=$proto && + export GIT_ALLOW_PROTOCOL && + git fetch + ) + ' + + test_expect_success "push $1 (enabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=$proto && + export GIT_ALLOW_PROTOCOL && + git push origin HEAD:pushed + ) + ' + + test_expect_success "push $1 (disabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git push origin HEAD:pushed + ) + ' + + test_expect_success "fetch $1 (disabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git fetch + ) + ' + + test_expect_success "clone $1 (disabled)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git clone --bare "$url" tmp.git + ) + ' +} + +# set up an ssh wrapper that will access $host/$repo in the +# trash directory, and enable it for subsequent tests. +setup_ssh_wrapper () { + test_expect_success 'setup ssh wrapper' ' + write_script ssh-wrapper <<-\EOF && + echo >&2 "ssh: $*" + host=$1; shift + cd "$TRASH_DIRECTORY/$host" && + eval "$*" + EOF + GIT_SSH="$PWD/ssh-wrapper" && + export GIT_SSH && + export TRASH_DIRECTORY + ' +} + +# set up a wrapper that can be used with remote-ext to +# access repositories in the "remote" directory of trash-dir, +# like "ext::fake-remote %S repo.git" +setup_ext_wrapper () { + test_expect_success 'setup ext wrapper' ' + write_script fake-remote <<-\EOF && + echo >&2 "fake-remote: $*" + cd "$TRASH_DIRECTORY/remote" && + eval "$*" + EOF + PATH=$TRASH_DIRECTORY:$PATH && + export TRASH_DIRECTORY + ' +} diff --git a/t/t5810-proto-disable-local.sh b/t/t5810-proto-disable-local.sh new file mode 100755 index 00000000000000..563592d8a8a5f6 --- /dev/null +++ b/t/t5810-proto-disable-local.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +test_description='test disabling of local paths in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" + +test_expect_success 'setup repository to clone' ' + test_commit one +' + +test_proto "file://" file "file://$PWD" +test_proto "path" file . + +test_done diff --git a/t/t5811-proto-disable-git.sh b/t/t5811-proto-disable-git.sh new file mode 100755 index 00000000000000..8ac6b2a1d0a286 --- /dev/null +++ b/t/t5811-proto-disable-git.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='test disabling of git-over-tcp in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" +. "$TEST_DIRECTORY/lib-git-daemon.sh" +start_git_daemon + +test_expect_success 'create git-accessible repo' ' + bare="$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" && + test_commit one && + git --bare init "$bare" && + git push "$bare" HEAD && + >"$bare/git-daemon-export-ok" && + git -C "$bare" config daemon.receivepack true +' + +test_proto "git://" git "$GIT_DAEMON_URL/repo.git" + +test_done diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh new file mode 100755 index 00000000000000..dd5001cbac8a0a --- /dev/null +++ b/t/t5812-proto-disable-http.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='test disabling of git-over-http in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" +. "$TEST_DIRECTORY/lib-httpd.sh" +start_httpd + +test_expect_success 'create git-accessible repo' ' + bare="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + test_commit one && + git --bare init "$bare" && + git push "$bare" HEAD && + git -C "$bare" config http.receivepack true +' + +test_proto "smart http" http "$HTTPD_URL/smart/repo.git" + +stop_httpd +test_done diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh new file mode 100755 index 00000000000000..ad877d774aad30 --- /dev/null +++ b/t/t5813-proto-disable-ssh.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='test disabling of git-over-ssh in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" + +setup_ssh_wrapper + +test_expect_success 'setup repository to clone' ' + test_commit one && + mkdir remote && + git init --bare remote/repo.git && + git push remote/repo.git HEAD +' + +test_proto "host:path" ssh "remote:repo.git" +test_proto "ssh://" ssh "ssh://remote/$PWD/remote/repo.git" +test_proto "git+ssh://" ssh "git+ssh://remote/$PWD/remote/repo.git" + +test_done diff --git a/t/t5814-proto-disable-ext.sh b/t/t5814-proto-disable-ext.sh new file mode 100755 index 00000000000000..9d6f7dfa2cc3da --- /dev/null +++ b/t/t5814-proto-disable-ext.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test_description='test disabling of remote-helper paths in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" + +setup_ext_wrapper + +test_expect_success 'setup repository to clone' ' + test_commit one && + mkdir remote && + git init --bare remote/repo.git && + git push remote/repo.git HEAD +' + +test_proto "remote-helper" ext "ext::fake-remote %S repo.git" + +test_done diff --git a/transport-helper.c b/transport-helper.c index 7dc4a443aee0c6..0b5362c268c38a 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1038,6 +1038,8 @@ int transport_helper_init(struct transport *transport, const char *name) struct helper_data *data = xcalloc(1, sizeof(*data)); data->name = name; + transport_check_allowed(name); + if (getenv("GIT_TRANSPORT_HELPER_DEBUG")) debug = 1; diff --git a/transport.c b/transport.c index 88bde1d85e48e7..94fe8658f2bfa7 100644 --- a/transport.c +++ b/transport.c @@ -909,6 +909,20 @@ static int external_specification_len(const char *url) return strchr(url, ':') - url; } +void transport_check_allowed(const char *type) +{ + struct string_list allowed = STRING_LIST_INIT_DUP; + const char *v = getenv("GIT_ALLOW_PROTOCOL"); + + if (!v) + return; + + string_list_split(&allowed, v, ':', -1); + if (!unsorted_string_list_has_string(&allowed, type)) + die("transport '%s' not allowed", type); + string_list_clear(&allowed, 0); +} + struct transport *transport_get(struct remote *remote, const char *url) { const char *helper; @@ -940,12 +954,14 @@ struct transport *transport_get(struct remote *remote, const char *url) if (helper) { transport_helper_init(ret, helper); } else if (starts_with(url, "rsync:")) { + transport_check_allowed("rsync"); ret->get_refs_list = get_refs_via_rsync; ret->fetch = fetch_objs_via_rsync; ret->push = rsync_transport_push; ret->smart_options = NULL; } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); + transport_check_allowed("file"); ret->data = data; ret->get_refs_list = get_refs_from_bundle; ret->fetch = fetch_refs_from_bundle; @@ -957,7 +973,10 @@ struct transport *transport_get(struct remote *remote, const char *url) || starts_with(url, "ssh://") || starts_with(url, "git+ssh://") || starts_with(url, "ssh+git://")) { - /* These are builtin smart transports. */ + /* + * These are builtin smart transports; "allowed" transports + * will be checked individually in git_connect. + */ struct git_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; ret->set_option = NULL; diff --git a/transport.h b/transport.h index 3e0091eaabe406..f7df6ec1d2a1f1 100644 --- a/transport.h +++ b/transport.h @@ -132,6 +132,13 @@ struct transport { /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); +/* + * Check whether a transport is allowed by the environment, + * and die otherwise. type should generally be the URL scheme, + * as described in Documentation/git.txt + */ +void transport_check_allowed(const char *type); + /* Transport options which apply to git:// and scp-style URLs */ /* The program to use on the remote side to send a pack */ From 33cfccbbf35a56e190b79bdec5c85457c952a021 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 16 Sep 2015 13:13:12 -0400 Subject: [PATCH 116/539] submodule: allow only certain protocols for submodule fetches Some protocols (like git-remote-ext) can execute arbitrary code found in the URL. The URLs that submodules use may come from arbitrary sources (e.g., .gitmodules files in a remote repository). Let's restrict submodules to fetching from a known-good subset of protocols. Note that we apply this restriction to all submodule commands, whether the URL comes from .gitmodules or not. This is more restrictive than we need to be; for example, in the tests we run: git submodule add ext::... which should be trusted, as the URL comes directly from the command line provided by the user. But doing it this way is simpler, and makes it much less likely that we would miss a case. And since such protocols should be an exception (especially because nobody who clones from them will be able to update the submodules!), it's not likely to inconvenience anyone in practice. Reported-by: Blake Burkhart Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-submodule.sh | 9 ++++++++ t/t5815-submodule-protos.sh | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100755 t/t5815-submodule-protos.sh diff --git a/git-submodule.sh b/git-submodule.sh index 36797c3c00f489..78c2740fdb2beb 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -22,6 +22,15 @@ require_work_tree wt_prefix=$(git rev-parse --show-prefix) cd_to_toplevel +# Restrict ourselves to a vanilla subset of protocols; the URLs +# we get are under control of a remote repository, and we do not +# want them kicking off arbitrary git-remote-* programs. +# +# If the user has already specified a set of allowed protocols, +# we assume they know what they're doing and use that instead. +: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh} +export GIT_ALLOW_PROTOCOL + command= branch= force= diff --git a/t/t5815-submodule-protos.sh b/t/t5815-submodule-protos.sh new file mode 100755 index 00000000000000..06f55a1b8a0b57 --- /dev/null +++ b/t/t5815-submodule-protos.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='test protocol whitelisting with submodules' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-proto-disable.sh + +setup_ext_wrapper +setup_ssh_wrapper + +test_expect_success 'setup repository with submodules' ' + mkdir remote && + git init remote/repo.git && + (cd remote/repo.git && test_commit one) && + # submodule-add should probably trust what we feed it on the cmdline, + # but its implementation is overly conservative. + GIT_ALLOW_PROTOCOL=ssh git submodule add remote:repo.git ssh-module && + GIT_ALLOW_PROTOCOL=ext git submodule add "ext::fake-remote %S repo.git" ext-module && + git commit -m "add submodules" +' + +test_expect_success 'clone with recurse-submodules fails' ' + test_must_fail git clone --recurse-submodules . dst +' + +test_expect_success 'setup individual updates' ' + rm -rf dst && + git clone . dst && + git -C dst submodule init +' + +test_expect_success 'update of ssh allowed' ' + git -C dst submodule update ssh-module +' + +test_expect_success 'update of ext not allowed' ' + test_must_fail git -C dst submodule update ext-module +' + +test_expect_success 'user can override whitelist' ' + GIT_ALLOW_PROTOCOL=ext git -C dst submodule update ext-module +' + +test_done From 1051e40dba16bd8e490c41ce926c8c36b913de72 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:06 +0530 Subject: [PATCH 117/539] branch: refactor width computation Remove unnecessary variables from ref_list and ref_item which were used for width computation. This is to make ref_item similar to ref-filter's ref_array_item. This will ensure a smooth port of branch.c to use ref-filter APIs in further patches. Previously the maxwidth was computed when inserting the refs into the ref_list. Now, we obtain the entire ref_list and then compute maxwidth. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 64 +++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 4fc8beb23c3693..28a10d60c66948 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -282,14 +282,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, struct ref_item { char *name; char *dest; - unsigned int kind, width; + unsigned int kind; struct commit *commit; int ignore; }; struct ref_list { struct rev_info revs; - int index, alloc, maxwidth, verbose, abbrev; + int index, alloc, verbose, abbrev; struct ref_item *list; struct commit_list *with_commit; int kinds; @@ -386,15 +386,8 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag newitem->name = xstrdup(refname); newitem->kind = kind; newitem->commit = commit; - newitem->width = utf8_strwidth(refname); newitem->dest = resolve_symref(orig_refname, prefix); newitem->ignore = 0; - /* adjust for "remotes/" */ - if (newitem->kind == REF_REMOTE_BRANCH && - ref_list->kinds != REF_REMOTE_BRANCH) - newitem->width += 8; - if (newitem->width > ref_list->maxwidth) - ref_list->maxwidth = newitem->width; return 0; } @@ -505,11 +498,12 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, } static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current, char *prefix) + int abbrev, int current, const char *remote_prefix) { char c; int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; + const char *prefix = ""; if (item->ignore) return; @@ -520,6 +514,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, break; case REF_REMOTE_BRANCH: color = BRANCH_COLOR_REMOTE; + prefix = remote_prefix; break; default: color = BRANCH_COLOR_PLAIN; @@ -557,16 +552,22 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, strbuf_release(&out); } -static int calc_maxwidth(struct ref_list *refs) +static int calc_maxwidth(struct ref_list *refs, int remote_bonus) { - int i, w = 0; + int i, max = 0; for (i = 0; i < refs->index; i++) { - if (refs->list[i].ignore) + struct ref_item *it = &refs->list[i]; + int w; + + if (it->ignore) continue; - if (refs->list[i].width > w) - w = refs->list[i].width; + w = utf8_strwidth(it->name); + if (it->kind == REF_REMOTE_BRANCH) + w += remote_bonus; + if (w > max) + max = w; } - return w; + return max; } static char *get_head_description(void) @@ -600,21 +601,18 @@ static char *get_head_description(void) return strbuf_detach(&desc, NULL); } -static void show_detached(struct ref_list *ref_list) +static void show_detached(struct ref_list *ref_list, int maxwidth) { struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { struct ref_item item; item.name = get_head_description(); - item.width = utf8_strwidth(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; item.commit = head_commit; item.ignore = 0; - if (item.width > ref_list->maxwidth) - ref_list->maxwidth = item.width; - print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); + print_ref_item(&item, maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); free(item.name); } } @@ -624,6 +622,16 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru int i; struct append_ref_cb cb; struct ref_list ref_list; + int maxwidth = 0; + const char *remote_prefix = ""; + + /* + * If we are listing more than just remote branches, + * then remote branches will have a "remotes/" prefix. + * We need to account for this in the width. + */ + if (kinds != REF_REMOTE_BRANCH) + remote_prefix = "remotes/"; memset(&ref_list, 0, sizeof(ref_list)); ref_list.kinds = kinds; @@ -667,26 +675,22 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru clear_commit_marks(item->commit, ALL_REV_FLAGS); } clear_commit_marks(filter, ALL_REV_FLAGS); - - if (verbose) - ref_list.maxwidth = calc_maxwidth(&ref_list); } + if (verbose) + maxwidth = calc_maxwidth(&ref_list, strlen(remote_prefix)); qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); if (detached && match_patterns(pattern, "HEAD")) - show_detached(&ref_list); + show_detached(&ref_list, maxwidth); for (i = 0; i < ref_list.index; i++) { int current = !detached && (ref_list.list[i].kind == REF_LOCAL_BRANCH) && !strcmp(ref_list.list[i].name, head); - char *prefix = (kinds != REF_REMOTE_BRANCH && - ref_list.list[i].kind == REF_REMOTE_BRANCH) - ? "remotes/" : ""; - print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current, prefix); + print_ref_item(&ref_list.list[i], maxwidth, verbose, + abbrev, current, remote_prefix); } free_ref_list(&ref_list); From 2dad24a5c39bb26a815c35c5db7a35499bad48e1 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:07 +0530 Subject: [PATCH 118/539] branch: bump get_head_description() to the top This is a preperatory patch for 'roll show_detached HEAD into regular ref_list'. This patch moves get_head_description() to the top so that it can be used in print_ref_item(). Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 62 ++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 28a10d60c66948..193296a6be3857 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -497,6 +497,37 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, strbuf_release(&subject); } +static char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) { + /* TRANSLATORS: make sure these match _("HEAD detached at ") + and _("HEAD detached from ") in wt-status.c */ + if (state.detached_at) + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} + static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int abbrev, int current, const char *remote_prefix) { @@ -570,37 +601,6 @@ static int calc_maxwidth(struct ref_list *refs, int remote_bonus) return max; } -static char *get_head_description(void) -{ - struct strbuf desc = STRBUF_INIT; - struct wt_status_state state; - memset(&state, 0, sizeof(state)); - wt_status_get_state(&state, 1); - if (state.rebase_in_progress || - state.rebase_interactive_in_progress) - strbuf_addf(&desc, _("(no branch, rebasing %s)"), - state.branch); - else if (state.bisect_in_progress) - strbuf_addf(&desc, _("(no branch, bisect started on %s)"), - state.branch); - else if (state.detached_from) { - /* TRANSLATORS: make sure these match _("HEAD detached at ") - and _("HEAD detached from ") in wt-status.c */ - if (state.detached_at) - strbuf_addf(&desc, _("(HEAD detached at %s)"), - state.detached_from); - else - strbuf_addf(&desc, _("(HEAD detached from %s)"), - state.detached_from); - } - else - strbuf_addstr(&desc, _("(no branch)")); - free(state.branch); - free(state.onto); - free(state.detached_from); - return strbuf_detach(&desc, NULL); -} - static void show_detached(struct ref_list *ref_list, int maxwidth) { struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); From 23e714df91cb8d824f6fd0594cd80c8a7dd9b751 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:08 +0530 Subject: [PATCH 119/539] branch: roll show_detached HEAD into regular ref_list Remove show_detached() and make detached HEAD to be rolled into regular ref_list by adding REF_DETACHED_HEAD as a kind of branch and supporting the same in append_ref(). This eliminates the need for an extra function and helps in easier porting of branch.c to use ref-filter APIs. Before show_detached() used to check if the HEAD branch satisfies the '--contains' option, now that is taken care by append_ref(). Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 61 ++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 193296a6be3857..a2a35f414636f6 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -28,8 +28,9 @@ static const char * const builtin_branch_usage[] = { NULL }; -#define REF_LOCAL_BRANCH 0x01 -#define REF_REMOTE_BRANCH 0x02 +#define REF_DETACHED_HEAD 0x01 +#define REF_LOCAL_BRANCH 0x02 +#define REF_REMOTE_BRANCH 0x04 static const char *head; static unsigned char head_sha1[20]; @@ -352,8 +353,12 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag break; } } - if (ARRAY_SIZE(ref_kind) <= i) - return 0; + if (ARRAY_SIZE(ref_kind) <= i) { + if (!strcmp(refname, "HEAD")) + kind = REF_DETACHED_HEAD; + else + return 0; + } /* Don't add types the caller doesn't want */ if ((kind & ref_list->kinds) == 0) @@ -535,6 +540,8 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; const char *prefix = ""; + const char *desc = item->name; + char *to_free = NULL; if (item->ignore) return; @@ -547,6 +554,10 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, color = BRANCH_COLOR_REMOTE; prefix = remote_prefix; break; + case REF_DETACHED_HEAD: + color = BRANCH_COLOR_CURRENT; + desc = to_free = get_head_description(); + break; default: color = BRANCH_COLOR_PLAIN; break; @@ -558,7 +569,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, color = BRANCH_COLOR_CURRENT; } - strbuf_addf(&name, "%s%s", prefix, item->name); + strbuf_addf(&name, "%s%s", prefix, desc); if (verbose) { int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), @@ -581,6 +592,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } strbuf_release(&name); strbuf_release(&out); + free(to_free); } static int calc_maxwidth(struct ref_list *refs, int remote_bonus) @@ -601,22 +613,6 @@ static int calc_maxwidth(struct ref_list *refs, int remote_bonus) return max; } -static void show_detached(struct ref_list *ref_list, int maxwidth) -{ - struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); - - if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { - struct ref_item item; - item.name = get_head_description(); - item.kind = REF_LOCAL_BRANCH; - item.dest = NULL; - item.commit = head_commit; - item.ignore = 0; - print_ref_item(&item, maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); - free(item.name); - } -} - static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) { int i; @@ -643,7 +639,14 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru cb.ref_list = &ref_list; cb.pattern = pattern; cb.ret = 0; + /* + * First we obtain all regular branch refs and if the HEAD is + * detached then we insert that ref to the end of the ref_fist + * so that it can be printed and removed first. + */ for_each_rawref(append_ref, &cb); + if (detached) + head_ref(append_ref, &cb); /* * The following implementation is currently duplicated in ref-filter. It * will eventually be removed when we port branch.c to use ref-filter APIs. @@ -681,14 +684,12 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached && match_patterns(pattern, "HEAD")) - show_detached(&ref_list, maxwidth); - for (i = 0; i < ref_list.index; i++) { - int current = !detached && - (ref_list.list[i].kind == REF_LOCAL_BRANCH) && + int current = !detached && (ref_list.list[i].kind == REF_LOCAL_BRANCH) && !strcmp(ref_list.list[i].name, head); + /* If detached the first ref_item is the current ref */ + if (detached && i == 0) + current = 1; print_ref_item(&ref_list.list[i], maxwidth, verbose, abbrev, current, remote_prefix); } @@ -914,7 +915,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("branch name required")); return delete_branches(argc, argv, delete > 1, kinds, quiet); } else if (list) { - int ret = print_ref_list(kinds, detached, verbose, abbrev, + int ret; + /* git branch --local also shows HEAD when it is detached */ + if (kinds & REF_LOCAL_BRANCH) + kinds |= REF_DETACHED_HEAD; + ret = print_ref_list(kinds, detached, verbose, abbrev, with_commit, argv); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); From f65f13911afa8dd66d163049d91dbfe2ed73717e Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:09 +0530 Subject: [PATCH 120/539] branch: move 'current' check down to the presentation layer We check if given ref is the current branch in print_ref_list(). Move this check to print_ref_item() where it is checked right before printing. This enables a smooth transition to using ref-filter APIs, as we can later replace the current check while printing to just check for FILTER_REFS_DETACHED instead. Based-on-patch-by: Jeff King Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index a2a35f414636f6..1a664ed315bc9a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -534,9 +534,10 @@ static char *get_head_description(void) } static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current, const char *remote_prefix) + int abbrev, int detached, const char *remote_prefix) { char c; + int current = 0; int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; const char *prefix = ""; @@ -548,15 +549,18 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, switch (item->kind) { case REF_LOCAL_BRANCH: - color = BRANCH_COLOR_LOCAL; + if (!detached && !strcmp(item->name, head)) + current = 1; + else + color = BRANCH_COLOR_LOCAL; break; case REF_REMOTE_BRANCH: color = BRANCH_COLOR_REMOTE; prefix = remote_prefix; break; case REF_DETACHED_HEAD: - color = BRANCH_COLOR_CURRENT; desc = to_free = get_head_description(); + current = 1; break; default: color = BRANCH_COLOR_PLAIN; @@ -684,15 +688,9 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - for (i = 0; i < ref_list.index; i++) { - int current = !detached && (ref_list.list[i].kind == REF_LOCAL_BRANCH) && - !strcmp(ref_list.list[i].name, head); - /* If detached the first ref_item is the current ref */ - if (detached && i == 0) - current = 1; + for (i = 0; i < ref_list.index; i++) print_ref_item(&ref_list.list[i], maxwidth, verbose, - abbrev, current, remote_prefix); - } + abbrev, detached, remote_prefix); free_ref_list(&ref_list); From 122f76f574ce260429bfbd11251eed15039e3469 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 23 Sep 2015 13:46:39 -0700 Subject: [PATCH 121/539] fsck: exit with non-zero when problems are found After finding some problems (e.g. a ref refs/heads/X points at an object that is not a commit) and issuing an error message, the program failed to signal the fact that it found an error by a non-zero exit status. Signed-off-by: Junio C Hamano --- builtin/fsck.c | 18 ++++++++++++++---- t/t1450-fsck.sh | 22 +++++++++++++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/builtin/fsck.c b/builtin/fsck.c index 4e8e2ee5b73937..63ab0bbb0c31e2 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -35,6 +35,7 @@ static int show_dangling = 1; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 #define ERROR_PACK 04 +#define ERROR_REFS 010 #ifdef NO_D_INO_IN_DIRENT #define SORT_DIRENT 0 @@ -495,8 +496,10 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, /* We'll continue with the rest despite the error.. */ return 0; } - if (obj->type != OBJ_COMMIT && is_branch(refname)) + if (obj->type != OBJ_COMMIT && is_branch(refname)) { error("%s: not a commit", refname); + errors_found |= ERROR_REFS; + } default_refs++; obj->used = 1; mark_object_reachable(obj); @@ -559,17 +562,23 @@ static int fsck_head_link(void) fprintf(stderr, "Checking HEAD link\n"); head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag); - if (!head_points_at) + if (!head_points_at) { + errors_found |= ERROR_REFS; return error("Invalid HEAD"); + } if (!strcmp(head_points_at, "HEAD")) /* detached HEAD */ null_is_error = 1; - else if (!starts_with(head_points_at, "refs/heads/")) + else if (!starts_with(head_points_at, "refs/heads/")) { + errors_found |= ERROR_REFS; return error("HEAD points to something strange (%s)", head_points_at); + } if (is_null_oid(&head_oid)) { - if (null_is_error) + if (null_is_error) { + errors_found |= ERROR_REFS; return error("HEAD: detached HEAD points at nothing"); + } fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", head_points_at + 11); } @@ -589,6 +598,7 @@ static int fsck_cache_tree(struct cache_tree *it) if (!obj) { error("%s: invalid sha1 pointer in cache-tree", sha1_to_hex(it->sha1)); + errors_found |= ERROR_REFS; return 1; } obj->used = 1; diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index cfb32b62420dc1..0ad04da01606d6 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -77,11 +77,31 @@ test_expect_success 'object with bad sha1' ' test_expect_success 'branch pointing to non-commit' ' git rev-parse HEAD^{tree} >.git/refs/heads/invalid && test_when_finished "git update-ref -d refs/heads/invalid" && - git fsck 2>out && + test_must_fail git fsck 2>out && cat out && grep "not a commit" out ' +test_expect_success 'HEAD link pointing at a funny object' ' + test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && + mv .git/HEAD .git/SAVED_HEAD && + echo 0000000000000000000000000000000000000000 >.git/HEAD && + # avoid corrupt/broken HEAD from interfering with repo discovery + test_must_fail env GIT_DIR=.git git fsck 2>out && + cat out && + grep "detached HEAD points" out +' + +test_expect_success 'HEAD link pointing at a funny place' ' + test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && + mv .git/HEAD .git/SAVED_HEAD && + echo "ref: refs/funny/place" >.git/HEAD && + # avoid corrupt/broken HEAD from interfering with repo discovery + test_must_fail env GIT_DIR=.git git fsck 2>out && + cat out && + grep "HEAD points to something strange" out +' + test_expect_success 'email without @ is okay' ' git cat-file commit HEAD >basis && sed "s/@/AT/" basis >okay && From 362d8b6e0d313e0e06553f5c390dc7f2172c0569 Mon Sep 17 00:00:00 2001 From: Stephan Beyer Date: Thu, 24 Sep 2015 20:12:22 +0200 Subject: [PATCH 122/539] t5561: get rid of racy appending to logfile The definition of log_div() appended information to the web server's logfile to make the test more readable. However, log_div() was called right after a request is served (which is done by git-http-backend); the web server waits for the git-http-backend process to exit before it writes to the log file. When the duration between serving a request and exiting was long, the log_div() output was written before the last request's log, and the test failed. (This duration could become especially long for PROFILE=GEN builds.) To get rid of this behavior, we should not change the logfile at all. This commit removes log_div() and its calls. The additional information is kept in the test (for readability reasons) but filtered out before comparing it to the actual logfile. Signed-off-by: Stephan Beyer Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- t/t5560-http-backend-noserver.sh | 4 ---- t/t5561-http-backend.sh | 8 +------- t/t556x_common | 12 ------------ 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh index 5abd11a5638299..9d54ef478c24c9 100755 --- a/t/t5560-http-backend-noserver.sh +++ b/t/t5560-http-backend-noserver.sh @@ -40,10 +40,6 @@ POST() { test_cmp exp act } -log_div() { - return 0 -} - . "$TEST_DIRECTORY"/t556x_common expect_aliased() { diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh index d23fb023848352..13f91d7d5a9d7c 100755 --- a/t/t5561-http-backend.sh +++ b/t/t5561-http-backend.sh @@ -35,15 +35,9 @@ POST() { test_cmp exp act } -log_div() { - echo >>"$HTTPD_ROOT_PATH"/access.log - echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log - echo "###" >>"$HTTPD_ROOT_PATH"/access.log -} - . "$TEST_DIRECTORY"/t556x_common -cat >exp <exp < Date: Thu, 24 Sep 2015 23:39:08 +0530 Subject: [PATCH 123/539] branch: drop non-commit error reporting Remove the error "branch '%s' does not point at a commit" in append_ref(), which reports branch refs which do not point to commits. Also remove the error "some refs could not be read" in print_ref_list() which is triggered as a consequence of the first error. The purpose of these codepaths is not to diagnose and report a repository corruption. If we care about such a corruption, we should report it from fsck instead, which we already do. This also helps in a smooth port of branch.c to use ref-filter APIs over the following patches. On the other hand, ref-filter ignores refs which do not point at commits silently. Based-on-patch-by: Jeff King Helped-by: Junio C Hamano Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 1a664ed315bc9a..ebc37423abb0fd 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -313,7 +313,6 @@ static char *resolve_symref(const char *src, const char *prefix) struct append_ref_cb { struct ref_list *ref_list; const char **pattern; - int ret; }; static int match_patterns(const char **pattern, const char *refname) @@ -370,10 +369,8 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag commit = NULL; if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) { - cb->ret = error(_("branch '%s' does not point at a commit"), refname); + if (!commit) return 0; - } /* Filter with with_commit if specified */ if (!is_descendant_of(commit, ref_list->with_commit)) @@ -617,7 +614,7 @@ static int calc_maxwidth(struct ref_list *refs, int remote_bonus) return max; } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) +static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) { int i; struct append_ref_cb cb; @@ -642,7 +639,6 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru init_revisions(&ref_list.revs, NULL); cb.ref_list = &ref_list; cb.pattern = pattern; - cb.ret = 0; /* * First we obtain all regular branch refs and if the HEAD is * detached then we insert that ref to the end of the ref_fist @@ -693,11 +689,6 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru abbrev, detached, remote_prefix); free_ref_list(&ref_list); - - if (cb.ret) - error(_("some refs could not be read")); - - return cb.ret; } static void rename_branch(const char *oldname, const char *newname, int force) @@ -913,15 +904,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("branch name required")); return delete_branches(argc, argv, delete > 1, kinds, quiet); } else if (list) { - int ret; /* git branch --local also shows HEAD when it is detached */ if (kinds & REF_LOCAL_BRANCH) kinds |= REF_DETACHED_HEAD; - ret = print_ref_list(kinds, detached, verbose, abbrev, + print_ref_list(kinds, detached, verbose, abbrev, with_commit, argv); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); - return ret; + return 0; } else if (edit_description) { const char *branch_name; From 1511b22d40d102f397104858a617aa1662bb1c98 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:11 +0530 Subject: [PATCH 124/539] branch.c: use 'ref-filter' data structures Make 'branch.c' use 'ref-filter' data structures and make changes to support the new data structures. This is a part of the process of porting 'branch.c' to use 'ref-filter' APIs. This is a temporary step before porting 'branch.c' to use 'ref-filter' completely. As this is a temporary step, most of the code introduced here will be removed when 'branch.c' is ported over to use 'ref-filter' APIs. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- builtin/branch.c | 317 +++++++++++++++++++---------------------------- ref-filter.h | 7 +- 2 files changed, 134 insertions(+), 190 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index ebc37423abb0fd..bfbba2f8d10501 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -19,6 +19,7 @@ #include "column.h" #include "utf8.h" #include "wt-status.h" +#include "ref-filter.h" static const char * const builtin_branch_usage[] = { N_("git branch [] [-r | -a] [--merged | --no-merged]"), @@ -28,10 +29,6 @@ static const char * const builtin_branch_usage[] = { NULL }; -#define REF_DETACHED_HEAD 0x01 -#define REF_LOCAL_BRANCH 0x02 -#define REF_REMOTE_BRANCH 0x04 - static const char *head; static unsigned char head_sha1[20]; @@ -53,13 +50,6 @@ enum color_branch { BRANCH_COLOR_UPSTREAM = 5 }; -static enum merge_filter { - NO_FILTER = 0, - SHOW_NOT_MERGED, - SHOW_MERGED -} merge_filter; -static unsigned char merge_filter_ref[20]; - static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; @@ -122,7 +112,7 @@ static int branch_merged(int kind, const char *name, void *reference_name_to_free = NULL; int merged; - if (kind == REF_LOCAL_BRANCH) { + if (kind == FILTER_REFS_BRANCHES) { struct branch *branch = branch_get(name); const char *upstream = branch_get_upstream(branch, NULL); unsigned char sha1[20]; @@ -200,14 +190,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, struct strbuf bname = STRBUF_INIT; switch (kinds) { - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: fmt = "refs/remotes/%s"; /* For subsequent UI messages */ remote_branch = 1; force = 1; break; - case REF_LOCAL_BRANCH: + case FILTER_REFS_BRANCHES: fmt = "refs/heads/%s"; break; default: @@ -224,7 +214,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { + if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) { error(_("Cannot delete the branch '%s' " "which you are currently on."), bname.buf); ret = 1; @@ -280,22 +270,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, return(ret); } -struct ref_item { - char *name; - char *dest; - unsigned int kind; - struct commit *commit; - int ignore; -}; - -struct ref_list { - struct rev_info revs; - int index, alloc, verbose, abbrev; - struct ref_item *list; - struct commit_list *with_commit; - int kinds; -}; - static char *resolve_symref(const char *src, const char *prefix) { unsigned char sha1[20]; @@ -310,11 +284,6 @@ static char *resolve_symref(const char *src, const char *prefix) return xstrdup(dst); } -struct append_ref_cb { - struct ref_list *ref_list; - const char **pattern; -}; - static int match_patterns(const char **pattern, const char *refname) { if (!*pattern) @@ -327,11 +296,29 @@ static int match_patterns(const char **pattern, const char *refname) return 0; } +/* + * Allocate memory for a new ref_array_item and insert that into the + * given ref_array. Doesn't take the objectname unlike + * new_ref_array_item(). This is a temporary function which will be + * removed when we port branch.c to use ref-filter APIs. + */ +static struct ref_array_item *ref_array_append(struct ref_array *array, const char *refname) +{ + size_t len = strlen(refname); + struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1); + memcpy(ref->refname, refname, len); + ref->refname[len] = '\0'; + REALLOC_ARRAY(array->items, array->nr + 1); + array->items[array->nr++] = ref; + return ref; +} + static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { - struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); - struct ref_list *ref_list = cb->ref_list; - struct ref_item *newitem; + struct ref_filter_cbdata *cb = (struct ref_filter_cbdata *)(cb_data); + struct ref_filter *filter = cb->filter; + struct ref_array *array = cb->array; + struct ref_array_item *item; struct commit *commit; int kind, i; const char *prefix, *orig_refname = refname; @@ -340,8 +327,8 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag int kind; const char *prefix; } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/" }, - { REF_REMOTE_BRANCH, "refs/remotes/" }, + { FILTER_REFS_BRANCHES, "refs/heads/" }, + { FILTER_REFS_REMOTES, "refs/remotes/" }, }; /* Detect kind */ @@ -354,65 +341,52 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag } if (ARRAY_SIZE(ref_kind) <= i) { if (!strcmp(refname, "HEAD")) - kind = REF_DETACHED_HEAD; + kind = FILTER_REFS_DETACHED_HEAD; else return 0; } /* Don't add types the caller doesn't want */ - if ((kind & ref_list->kinds) == 0) + if ((kind & filter->kind) == 0) return 0; - if (!match_patterns(cb->pattern, refname)) + if (!match_patterns(filter->name_patterns, refname)) return 0; commit = NULL; - if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { + if (filter->verbose || filter->with_commit || filter->merge != REF_FILTER_MERGED_NONE) { commit = lookup_commit_reference_gently(oid->hash, 1); if (!commit) return 0; /* Filter with with_commit if specified */ - if (!is_descendant_of(commit, ref_list->with_commit)) + if (!is_descendant_of(commit, filter->with_commit)) return 0; - if (merge_filter != NO_FILTER) - add_pending_object(&ref_list->revs, + if (filter->merge != REF_FILTER_MERGED_NONE) + add_pending_object(array->revs, (struct object *)commit, refname); } - ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc); + item = ref_array_append(array, refname); /* Record the new item */ - newitem = &(ref_list->list[ref_list->index++]); - newitem->name = xstrdup(refname); - newitem->kind = kind; - newitem->commit = commit; - newitem->dest = resolve_symref(orig_refname, prefix); - newitem->ignore = 0; + item->kind = kind; + item->commit = commit; + item->symref = resolve_symref(orig_refname, prefix); + item->ignore = 0; return 0; } -static void free_ref_list(struct ref_list *ref_list) -{ - int i; - - for (i = 0; i < ref_list->index; i++) { - free(ref_list->list[i].name); - free(ref_list->list[i].dest); - } - free(ref_list->list); -} - static int ref_cmp(const void *r1, const void *r2) { - struct ref_item *c1 = (struct ref_item *)(r1); - struct ref_item *c2 = (struct ref_item *)(r2); + struct ref_array_item *c1 = *((struct ref_array_item **)r1); + struct ref_array_item *c2 = *((struct ref_array_item **)r2); if (c1->kind != c2->kind) return c1->kind - c2->kind; - return strcmp(c1->name, c2->name); + return strcmp(c1->refname, c2->refname); } static void fill_tracking_info(struct strbuf *stat, const char *branch_name, @@ -477,8 +451,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, free(ref); } -static void add_verbose_info(struct strbuf *out, struct ref_item *item, - int verbose, int abbrev) +static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, + struct ref_filter *filter) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; const char *sub = _(" **** invalid ref ****"); @@ -489,11 +463,11 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, sub = subject.buf; } - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); + if (item->kind == FILTER_REFS_BRANCHES) + fill_tracking_info(&stat, item->refname, filter->verbose > 1); strbuf_addf(out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), + find_unique_abbrev(item->commit->object.sha1, filter->abbrev), stat.buf, sub); strbuf_release(&stat); strbuf_release(&subject); @@ -530,32 +504,32 @@ static char *get_head_description(void) return strbuf_detach(&desc, NULL); } -static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int detached, const char *remote_prefix) +static void print_ref_item(struct ref_array_item *item, int maxwidth, + struct ref_filter *filter, const char *remote_prefix) { char c; int current = 0; int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; const char *prefix = ""; - const char *desc = item->name; + const char *desc = item->refname; char *to_free = NULL; if (item->ignore) return; switch (item->kind) { - case REF_LOCAL_BRANCH: - if (!detached && !strcmp(item->name, head)) + case FILTER_REFS_BRANCHES: + if (!filter->detached && !strcmp(item->refname, head)) current = 1; else color = BRANCH_COLOR_LOCAL; break; - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: color = BRANCH_COLOR_REMOTE; prefix = remote_prefix; break; - case REF_DETACHED_HEAD: + case FILTER_REFS_DETACHED_HEAD: desc = to_free = get_head_description(); current = 1; break; @@ -571,7 +545,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } strbuf_addf(&name, "%s%s", prefix, desc); - if (verbose) { + if (filter->verbose) { int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), maxwidth + utf8_compensation, name.buf, @@ -580,13 +554,13 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), name.buf, branch_get_color(BRANCH_COLOR_RESET)); - if (item->dest) - strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) + if (item->symref) + strbuf_addf(&out, " -> %s", item->symref); + else if (filter->verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, verbose, abbrev); + add_verbose_info(&out, item, filter); if (column_active(colopts)) { - assert(!verbose && "--column and --verbose are incompatible"); + assert(!filter->verbose && "--column and --verbose are incompatible"); string_list_append(&output, out.buf); } else { printf("%s\n", out.buf); @@ -596,17 +570,17 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, free(to_free); } -static int calc_maxwidth(struct ref_list *refs, int remote_bonus) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { int i, max = 0; - for (i = 0; i < refs->index; i++) { - struct ref_item *it = &refs->list[i]; + for (i = 0; i < refs->nr; i++) { + struct ref_array_item *it = refs->items[i]; int w; if (it->ignore) continue; - w = utf8_strwidth(it->name); - if (it->kind == REF_REMOTE_BRANCH) + w = utf8_strwidth(it->refname); + if (it->kind == FILTER_REFS_REMOTES) w += remote_bonus; if (w > max) max = w; @@ -614,81 +588,74 @@ static int calc_maxwidth(struct ref_list *refs, int remote_bonus) return max; } -static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) +static void print_ref_list(struct ref_filter *filter) { int i; - struct append_ref_cb cb; - struct ref_list ref_list; + struct ref_array array; + struct ref_filter_cbdata data; int maxwidth = 0; const char *remote_prefix = ""; + struct rev_info revs; /* * If we are listing more than just remote branches, * then remote branches will have a "remotes/" prefix. * We need to account for this in the width. */ - if (kinds != REF_REMOTE_BRANCH) + if (filter->kind != FILTER_REFS_REMOTES) remote_prefix = "remotes/"; - memset(&ref_list, 0, sizeof(ref_list)); - ref_list.kinds = kinds; - ref_list.verbose = verbose; - ref_list.abbrev = abbrev; - ref_list.with_commit = with_commit; - if (merge_filter != NO_FILTER) - init_revisions(&ref_list.revs, NULL); - cb.ref_list = &ref_list; - cb.pattern = pattern; + memset(&array, 0, sizeof(array)); + if (filter->merge != REF_FILTER_MERGED_NONE) + init_revisions(&revs, NULL); + + data.array = &array; + data.filter = filter; + array.revs = &revs; + /* * First we obtain all regular branch refs and if the HEAD is * detached then we insert that ref to the end of the ref_fist * so that it can be printed and removed first. */ - for_each_rawref(append_ref, &cb); - if (detached) - head_ref(append_ref, &cb); + for_each_rawref(append_ref, &data); + if (filter->detached) + head_ref(append_ref, &data); /* * The following implementation is currently duplicated in ref-filter. It * will eventually be removed when we port branch.c to use ref-filter APIs. */ - if (merge_filter != NO_FILTER) { - struct commit *filter; - filter = lookup_commit_reference_gently(merge_filter_ref, 0); - if (!filter) - die(_("object '%s' does not point to a commit"), - sha1_to_hex(merge_filter_ref)); - - filter->object.flags |= UNINTERESTING; - add_pending_object(&ref_list.revs, - (struct object *) filter, ""); - ref_list.revs.limited = 1; - - if (prepare_revision_walk(&ref_list.revs)) + if (filter->merge != REF_FILTER_MERGED_NONE) { + filter->merge_commit->object.flags |= UNINTERESTING; + add_pending_object(&revs, &filter->merge_commit->object, ""); + revs.limited = 1; + + if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; + for (i = 0; i < array.nr; i++) { + struct ref_array_item *item = array.items[i]; struct commit *commit = item->commit; int is_merged = !!(commit->object.flags & UNINTERESTING); - item->ignore = is_merged != (merge_filter == SHOW_MERGED); + item->ignore = is_merged != (filter->merge == REF_FILTER_MERGED_INCLUDE); } - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; + for (i = 0; i < array.nr; i++) { + struct ref_array_item *item = array.items[i]; clear_commit_marks(item->commit, ALL_REV_FLAGS); } - clear_commit_marks(filter, ALL_REV_FLAGS); + clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS); } - if (verbose) - maxwidth = calc_maxwidth(&ref_list, strlen(remote_prefix)); - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); + if (filter->verbose) + maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); + + qsort(array.items, array.nr, sizeof(struct ref_array_item *), ref_cmp); - for (i = 0; i < ref_list.index; i++) - print_ref_item(&ref_list.list[i], maxwidth, verbose, - abbrev, detached, remote_prefix); + for (i = 0; i < array.nr; i++) + print_ref_item(array.items[i], maxwidth, filter, remote_prefix); - free_ref_list(&ref_list); + ref_array_clear(&array); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -744,24 +711,6 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&newsection); } -/* - * This function is duplicated in ref-filter. It will eventually be removed - * when we port branch.c to use ref-filter APIs. - */ -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) -{ - merge_filter = ((opt->long_name[0] == 'n') - ? SHOW_NOT_MERGED - : SHOW_MERGED); - if (unset) - merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ - if (!arg) - arg = "HEAD"; - if (get_sha1(arg, merge_filter_ref)) - die(_("malformed object name %s"), arg); - return 0; -} - static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) @@ -801,17 +750,15 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force = 0, list = 0; - int verbose = 0, abbrev = -1, detached = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; enum branch_track track; - int kinds = REF_LOCAL_BRANCH; - struct commit_list *with_commit = NULL; + struct ref_filter filter; struct option options[] = { OPT_GROUP(N_("Generic options")), - OPT__VERBOSE(&verbose, + OPT__VERBOSE(&filter.verbose, N_("show hash and subject, give twice for upstream branch")), OPT__QUIET(&quiet, N_("suppress informational messages")), OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), @@ -821,15 +768,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"), OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"), OPT__COLOR(&branch_use_color, N_("use colored output")), - OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"), - REF_REMOTE_BRANCH), - OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")), - OPT_WITH(&with_commit, N_("print only branches that contain the commit")), - OPT__ABBREV(&abbrev), + OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), + FILTER_REFS_REMOTES), + OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")), + OPT__ABBREV(&filter.abbrev), OPT_GROUP(N_("Specific git-branch actions:")), - OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"), - REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), + OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"), + FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES), OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1), OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), @@ -839,22 +786,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), OPT__FORCE(&force, N_("force creation, move/rename, deletion")), - { - OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, - N_("commit"), N_("print only not merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - { - OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - N_("commit"), N_("print only merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, + OPT_MERGED(&filter, N_("print only branches that are merged")), + OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_END(), }; + memset(&filter, 0, sizeof(filter)); + filter.kind = FILTER_REFS_BRANCHES; + filter.abbrev = -1; + if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); @@ -866,11 +807,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) - detached = 1; + filter.detached = 1; else if (!skip_prefix(head, "refs/heads/", &head)) die(_("HEAD not found below refs/heads!")); - hashcpy(merge_filter_ref, head_sha1); - argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); @@ -878,17 +817,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; - if (with_commit || merge_filter != NO_FILTER) + if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE) list = 1; if (!!delete + !!rename + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); - if (abbrev == -1) - abbrev = DEFAULT_ABBREV; + if (filter.abbrev == -1) + filter.abbrev = DEFAULT_ABBREV; finalize_colopts(&colopts, -1); - if (verbose) { + if (filter.verbose) { if (explicitly_enable_column(colopts)) die(_("--column and --verbose are incompatible")); colopts = 0; @@ -902,13 +841,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) { if (!argc) die(_("branch name required")); - return delete_branches(argc, argv, delete > 1, kinds, quiet); + return delete_branches(argc, argv, delete > 1, filter.kind, quiet); } else if (list) { /* git branch --local also shows HEAD when it is detached */ - if (kinds & REF_LOCAL_BRANCH) - kinds |= REF_DETACHED_HEAD; - print_ref_list(kinds, detached, verbose, abbrev, - with_commit, argv); + if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) + filter.kind |= FILTER_REFS_DETACHED_HEAD; + filter.name_patterns = argv; + print_ref_list(&filter); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; @@ -918,7 +857,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct strbuf branch_ref = STRBUF_INIT; if (!argc) { - if (detached) + if (filter.detached) die(_("Cannot give description to detached HEAD")); branch_name = head; } else if (argc == 1) @@ -1006,7 +945,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!branch) die(_("no such branch '%s'"), argv[0]); - if (kinds != REF_LOCAL_BRANCH) + if (filter.kind != FILTER_REFS_BRANCHES) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); if (track == BRANCH_TRACK_OVERRIDE) diff --git a/ref-filter.h b/ref-filter.h index a5cfa5e677dbdf..fadebc5da60032 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -36,6 +36,7 @@ struct ref_array_item { unsigned char objectname[20]; int flag; unsigned int kind; + int ignore : 1; /* To be removed in the next patch */ const char *symref; struct commit *commit; struct atom_value *value; @@ -45,6 +46,7 @@ struct ref_array_item { struct ref_array { int nr, alloc; struct ref_array_item **items; + struct rev_info *revs; }; struct ref_filter { @@ -60,9 +62,12 @@ struct ref_filter { struct commit *merge_commit; unsigned int with_commit_tag_algo : 1, - match_as_path : 1; + match_as_path : 1, + detached : 1; unsigned int kind, lines; + int abbrev, + verbose; }; struct ref_filter_cbdata { From aedcb7dc75e5c260f20bebe14925f3ac4841b03d Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:12 +0530 Subject: [PATCH 125/539] branch.c: use 'ref-filter' APIs Make 'branch.c' use 'ref-filter' APIs for iterating through refs sorting. This removes most of the code used in 'branch.c' replacing it with calls to the 'ref-filter' library. Make 'branch.c' use the 'filter_refs()' function provided by 'ref-filter' to filter out tags based on the options set. We provide a sorting option provided for 'branch.c' by using the sorting options provided by 'ref-filter'. Also by default, we sort by 'refname'. Since 'HEAD' is alphabatically before 'refs/...' we end up with an array consisting of the 'HEAD' ref then the local branches and finally the remote-tracking branches. Also remove the 'ignore' variable from ref_array_item as it was previously used for the '--merged' option and now that is handled by ref-filter. Modify some of the tests in t1430 to check the stderr for a warning regarding the broken ref. This is done as ref-filter throws a warning for broken refs rather than directly printing them. Add tests and documentation for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 12 +- builtin/branch.c | 213 ++++++----------------------------- ref-filter.c | 2 +- ref-filter.h | 1 - t/t1430-bad-ref-name.sh | 31 +++-- t/t3203-branch-output.sh | 11 ++ 6 files changed, 78 insertions(+), 192 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index a67138a022f1ba..c45295d9887572 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git branch' [--color[=] | --no-color] [-r | -a] [--list] [-v [--abbrev= | --no-abbrev]] [--column[=] | --no-column] - [(--merged | --no-merged | --contains) []] [...] + [(--merged | --no-merged | --contains) []] [--sort=] [...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] 'git branch' (--set-upstream-to= | -u ) [] 'git branch' --unset-upstream [] @@ -229,6 +229,16 @@ start-point is either a local or remote-tracking branch. The new name for an existing branch. The same restrictions as for apply. +--sort=:: + Sort based on the key given. Prefix `-` to sort in descending + order of the value. You may use the --sort= option + multiple times, in which case the last key becomes the primary + key. The keys supported are the same as those in `git + for-each-ref`. Sort order defaults to sorting based on the + full refname (including `refs/...` prefix). This lists + detached HEAD (if present) first, then local branches and + finally remote-tracking branches. + Examples -------- diff --git a/builtin/branch.c b/builtin/branch.c index bfbba2f8d10501..b83116a609d90b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -270,125 +270,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, return(ret); } -static char *resolve_symref(const char *src, const char *prefix) -{ - unsigned char sha1[20]; - int flag; - const char *dst; - - dst = resolve_ref_unsafe(src, 0, sha1, &flag); - if (!(dst && (flag & REF_ISSYMREF))) - return NULL; - if (prefix) - skip_prefix(dst, prefix, &dst); - return xstrdup(dst); -} - -static int match_patterns(const char **pattern, const char *refname) -{ - if (!*pattern) - return 1; /* no pattern always matches */ - while (*pattern) { - if (!wildmatch(*pattern, refname, 0, NULL)) - return 1; - pattern++; - } - return 0; -} - -/* - * Allocate memory for a new ref_array_item and insert that into the - * given ref_array. Doesn't take the objectname unlike - * new_ref_array_item(). This is a temporary function which will be - * removed when we port branch.c to use ref-filter APIs. - */ -static struct ref_array_item *ref_array_append(struct ref_array *array, const char *refname) -{ - size_t len = strlen(refname); - struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1); - memcpy(ref->refname, refname, len); - ref->refname[len] = '\0'; - REALLOC_ARRAY(array->items, array->nr + 1); - array->items[array->nr++] = ref; - return ref; -} - -static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) -{ - struct ref_filter_cbdata *cb = (struct ref_filter_cbdata *)(cb_data); - struct ref_filter *filter = cb->filter; - struct ref_array *array = cb->array; - struct ref_array_item *item; - struct commit *commit; - int kind, i; - const char *prefix, *orig_refname = refname; - - static struct { - int kind; - const char *prefix; - } ref_kind[] = { - { FILTER_REFS_BRANCHES, "refs/heads/" }, - { FILTER_REFS_REMOTES, "refs/remotes/" }, - }; - - /* Detect kind */ - for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { - prefix = ref_kind[i].prefix; - if (skip_prefix(refname, prefix, &refname)) { - kind = ref_kind[i].kind; - break; - } - } - if (ARRAY_SIZE(ref_kind) <= i) { - if (!strcmp(refname, "HEAD")) - kind = FILTER_REFS_DETACHED_HEAD; - else - return 0; - } - - /* Don't add types the caller doesn't want */ - if ((kind & filter->kind) == 0) - return 0; - - if (!match_patterns(filter->name_patterns, refname)) - return 0; - - commit = NULL; - if (filter->verbose || filter->with_commit || filter->merge != REF_FILTER_MERGED_NONE) { - commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) - return 0; - - /* Filter with with_commit if specified */ - if (!is_descendant_of(commit, filter->with_commit)) - return 0; - - if (filter->merge != REF_FILTER_MERGED_NONE) - add_pending_object(array->revs, - (struct object *)commit, refname); - } - - item = ref_array_append(array, refname); - - /* Record the new item */ - item->kind = kind; - item->commit = commit; - item->symref = resolve_symref(orig_refname, prefix); - item->ignore = 0; - - return 0; -} - -static int ref_cmp(const void *r1, const void *r2) -{ - struct ref_array_item *c1 = *((struct ref_array_item **)r1); - struct ref_array_item *c2 = *((struct ref_array_item **)r2); - - if (c1->kind != c2->kind) - return c1->kind - c2->kind; - return strcmp(c1->refname, c2->refname); -} - static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int show_upstream_ref) { @@ -452,7 +333,7 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, } static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, - struct ref_filter *filter) + struct ref_filter *filter, const char *refname) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; const char *sub = _(" **** invalid ref ****"); @@ -464,7 +345,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, } if (item->kind == FILTER_REFS_BRANCHES) - fill_tracking_info(&stat, item->refname, filter->verbose > 1); + fill_tracking_info(&stat, refname, filter->verbose > 1); strbuf_addf(out, " %s %s%s", find_unique_abbrev(item->commit->object.sha1, filter->abbrev), @@ -504,8 +385,8 @@ static char *get_head_description(void) return strbuf_detach(&desc, NULL); } -static void print_ref_item(struct ref_array_item *item, int maxwidth, - struct ref_filter *filter, const char *remote_prefix) +static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, + struct ref_filter *filter, const char *remote_prefix) { char c; int current = 0; @@ -515,17 +396,16 @@ static void print_ref_item(struct ref_array_item *item, int maxwidth, const char *desc = item->refname; char *to_free = NULL; - if (item->ignore) - return; - switch (item->kind) { case FILTER_REFS_BRANCHES: - if (!filter->detached && !strcmp(item->refname, head)) + skip_prefix(desc, "refs/heads/", &desc); + if (!filter->detached && !strcmp(desc, head)) current = 1; else color = BRANCH_COLOR_LOCAL; break; case FILTER_REFS_REMOTES: + skip_prefix(desc, "refs/remotes/", &desc); color = BRANCH_COLOR_REMOTE; prefix = remote_prefix; break; @@ -554,11 +434,13 @@ static void print_ref_item(struct ref_array_item *item, int maxwidth, strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), name.buf, branch_get_color(BRANCH_COLOR_RESET)); - if (item->symref) - strbuf_addf(&out, " -> %s", item->symref); + if (item->symref) { + skip_prefix(item->symref, "refs/remotes/", &desc); + strbuf_addf(&out, " -> %s", desc); + } else if (filter->verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, filter); + add_verbose_info(&out, item, filter, desc); if (column_active(colopts)) { assert(!filter->verbose && "--column and --verbose are incompatible"); string_list_append(&output, out.buf); @@ -575,11 +457,13 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus) int i, max = 0; for (i = 0; i < refs->nr; i++) { struct ref_array_item *it = refs->items[i]; + const char *desc = it->refname; int w; - if (it->ignore) - continue; - w = utf8_strwidth(it->refname); + skip_prefix(it->refname, "refs/heads/", &desc); + skip_prefix(it->refname, "refs/remotes/", &desc); + w = utf8_strwidth(desc); + if (it->kind == FILTER_REFS_REMOTES) w += remote_bonus; if (w > max) @@ -588,14 +472,12 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus) return max; } -static void print_ref_list(struct ref_filter *filter) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting) { int i; struct ref_array array; - struct ref_filter_cbdata data; int maxwidth = 0; const char *remote_prefix = ""; - struct rev_info revs; /* * If we are listing more than just remote branches, @@ -606,54 +488,26 @@ static void print_ref_list(struct ref_filter *filter) remote_prefix = "remotes/"; memset(&array, 0, sizeof(array)); - if (filter->merge != REF_FILTER_MERGED_NONE) - init_revisions(&revs, NULL); - - data.array = &array; - data.filter = filter; - array.revs = &revs; - - /* - * First we obtain all regular branch refs and if the HEAD is - * detached then we insert that ref to the end of the ref_fist - * so that it can be printed and removed first. - */ - for_each_rawref(append_ref, &data); - if (filter->detached) - head_ref(append_ref, &data); - /* - * The following implementation is currently duplicated in ref-filter. It - * will eventually be removed when we port branch.c to use ref-filter APIs. - */ - if (filter->merge != REF_FILTER_MERGED_NONE) { - filter->merge_commit->object.flags |= UNINTERESTING; - add_pending_object(&revs, &filter->merge_commit->object, ""); - revs.limited = 1; - - if (prepare_revision_walk(&revs)) - die(_("revision walk setup failed")); - - for (i = 0; i < array.nr; i++) { - struct ref_array_item *item = array.items[i]; - struct commit *commit = item->commit; - int is_merged = !!(commit->object.flags & UNINTERESTING); - item->ignore = is_merged != (filter->merge == REF_FILTER_MERGED_INCLUDE); - } - for (i = 0; i < array.nr; i++) { - struct ref_array_item *item = array.items[i]; - clear_commit_marks(item->commit, ALL_REV_FLAGS); - } - clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS); - } + verify_ref_format("%(refname)%(symref)"); + filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - qsort(array.items, array.nr, sizeof(struct ref_array_item *), ref_cmp); + /* + * If no sorting parameter is given then we default to sorting + * by 'refname'. This would give us an alphabetically sorted + * array with the 'HEAD' ref at the beginning followed by + * local branches 'refs/heads/...' and finally remote-tacking + * branches 'refs/remotes/...'. + */ + if (!sorting) + sorting = ref_default_sorting(); + ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) - print_ref_item(array.items[i], maxwidth, filter, remote_prefix); + format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix); ref_array_clear(&array); } @@ -755,6 +609,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) const char *new_upstream = NULL; enum branch_track track; struct ref_filter filter; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { OPT_GROUP(N_("Generic options")), @@ -789,6 +644,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_MERGED(&filter, N_("print only branches that are merged")), OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), OPT_END(), }; @@ -847,7 +704,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) filter.kind |= FILTER_REFS_DETACHED_HEAD; filter.name_patterns = argv; - print_ref_list(&filter); + print_ref_list(&filter, sorting); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; diff --git a/ref-filter.c b/ref-filter.c index fd839ac4b337d3..dbd8fcec24da01 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1331,7 +1331,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, * obtain the commit using the 'oid' available and discard all * non-commits early. The actual filtering is done later. */ - if (filter->merge_commit || filter->with_commit) { + if (filter->merge_commit || filter->with_commit || filter->verbose) { commit = lookup_commit_reference_gently(oid->hash, 1); if (!commit) return 0; diff --git a/ref-filter.h b/ref-filter.h index fadebc5da60032..14d435e2ccf020 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -36,7 +36,6 @@ struct ref_array_item { unsigned char objectname[20]; int flag; unsigned int kind; - int ignore : 1; /* To be removed in the next patch */ const char *symref; struct commit *commit; struct atom_value *value; diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index 16d0b8bd1a5b6b..c465abe8e34936 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -38,18 +38,20 @@ test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"' test_must_fail git fast-import output && - grep -e "broken\.\.\.ref" output + git branch >output 2>error && + grep -e "broken\.\.\.ref" error && + ! grep -e "broken\.\.\.ref" output ' test_expect_success 'branch -d can delete badly named ref' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch -d broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -57,7 +59,8 @@ test_expect_success 'branch -D can delete badly named ref' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch -D broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -85,7 +88,8 @@ test_expect_success 'branch -D cannot delete absolute path' ' test_expect_success 'git branch cannot create a badly named ref' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && test_must_fail git branch broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -95,7 +99,8 @@ test_expect_success 'branch -m cannot rename to a bad ref name' ' git branch goodref && test_must_fail git branch -m goodref broken...ref && test_cmp_rev master goodref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -104,14 +109,16 @@ test_expect_failure 'branch -m can rename from a bad ref name' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch -m broken...ref renamed && test_cmp_rev master renamed && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' test_expect_success 'push cannot create a badly named ref' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -131,7 +138,8 @@ test_expect_failure 'push --mirror can delete badly named ref' ' cp .git/refs/heads/master .git/refs/heads/broken...ref ) && git -C src push --mirror "file://$top/dest" && - git -C dest branch >output && + git -C dest branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -159,7 +167,8 @@ test_expect_success 'update-ref -d can delete broken name' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && git update-ref -d refs/heads/broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index f51d0f3cadcb4f..a427163c4d31c3 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -143,4 +143,15 @@ EOF test_i18ncmp expect actual ' +test_expect_success 'git branch `--sort` option' ' + cat >expect <<-\EOF && + branch-two + * (HEAD detached from fromtag) + branch-one + master + EOF + git branch --sort=objectsize >actual && + test_i18ncmp expect actual +' + test_done From aa3bc55e408d4daab52239d6b80f7d4bb87f6de7 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 23 Sep 2015 23:41:13 +0530 Subject: [PATCH 126/539] branch: add '--points-at' option Add the '--points-at' option provided by 'ref-filter'. The option lets the user to list only branches which points at the given object. Add documentation and tests for the same. Mentored-by: Christian Couder Mentored-by: Matthieu Moy Signed-off-by: Karthik Nayak Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 6 +++++- builtin/branch.c | 7 ++++++- t/t3203-branch-output.sh | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index c45295d9887572..03c7af1b905fd1 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -11,7 +11,8 @@ SYNOPSIS 'git branch' [--color[=] | --no-color] [-r | -a] [--list] [-v [--abbrev= | --no-abbrev]] [--column[=] | --no-column] - [(--merged | --no-merged | --contains) []] [--sort=] [...] + [(--merged | --no-merged | --contains) []] [--sort=] + [--points-at ] [...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] 'git branch' (--set-upstream-to= | -u ) [] 'git branch' --unset-upstream [] @@ -240,6 +241,9 @@ start-point is either a local or remote-tracking branch. finally remote-tracking branches. +--points-at :: + Only list branches of the given object. + Examples -------- diff --git a/builtin/branch.c b/builtin/branch.c index b83116a609d90b..b7a60f41e7012f 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -26,6 +26,7 @@ static const char * const builtin_branch_usage[] = { N_("git branch [] [-l] [-f] []"), N_("git branch [] [-r] (-d | -D) ..."), N_("git branch [] (-m | -M) [] "), + N_("git branch [] [-r | -a] [--points-at]"), NULL }; @@ -646,6 +647,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), N_("field name to sort on"), &parse_opt_ref_sorting), + { + OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), + N_("print only branches of the object"), 0, parse_opt_object_name + }, OPT_END(), }; @@ -674,7 +679,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; - if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE) + if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!new_upstream + diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index a427163c4d31c3..f1ae5ff662efa3 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -154,4 +154,13 @@ test_expect_success 'git branch `--sort` option' ' test_i18ncmp expect actual ' +test_expect_success 'git branch --points-at option' ' + cat >expect <<-\EOF && + branch-one + master + EOF + git branch --points-at=branch-one >actual && + test_cmp expect actual +' + test_done From 83e6bda3fa564fe6b1946712a17efce1d10ed2c0 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 24 Sep 2015 14:44:49 +0200 Subject: [PATCH 127/539] connect: fix typo in result string of prot_name() Replace 'unkown' with 'unknown'. Signed-off-by: Tobias Klauser Signed-off-by: Junio C Hamano --- connect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connect.c b/connect.c index c0144d859ae427..777f31c249c0a9 100644 --- a/connect.c +++ b/connect.c @@ -254,7 +254,7 @@ static const char *prot_name(enum protocol protocol) case PROTO_GIT: return "git"; default: - return "unkown protocol"; + return "unknown protocol"; } } From 7cd17e80579c2bffd6245837175a6e1b12a78045 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:02:54 -0400 Subject: [PATCH 128/539] show-branch: avoid segfault with --reflog of unborn branch When no branch is given to the "--reflog" option, we resolve HEAD to get the default branch. However, if HEAD points to an unborn branch, resolve_ref returns NULL, and we later segfault trying to access it. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/show-branch.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 408ce7030731f7..092b59b0b3c078 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -743,6 +743,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) fake_av[1] = NULL; av = fake_av; ac = 1; + if (!*av) + die("no branches given, and HEAD is not valid"); } if (ac != 1) die("--reflog option needs one branch name"); From d270d7b7a2d631c3d11315f20bb0cf15e438dafa Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:03:05 -0400 Subject: [PATCH 129/539] mailsplit: fix FILE* leak in split_maildir If we encounter an error while splitting a maildir, we exit the function early, leaking the open filehandle. This isn't a big deal, since we exit the program soon after, but it's easy enough to be careful. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/mailsplit.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 8e02ea109ac8f9..9de06e3cf7f9d1 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -150,6 +150,7 @@ static int split_maildir(const char *maildir, const char *dir, { char file[PATH_MAX]; char name[PATH_MAX]; + FILE *f = NULL; int ret = -1; int i; struct string_list list = STRING_LIST_INIT_DUP; @@ -160,7 +161,6 @@ static int split_maildir(const char *maildir, const char *dir, goto out; for (i = 0; i < list.nr; i++) { - FILE *f; snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string); f = fopen(file, "r"); if (!f) { @@ -177,10 +177,13 @@ static int split_maildir(const char *maildir, const char *dir, split_one(f, name, 1); fclose(f); + f = NULL; } ret = skip; out: + if (f) + fclose(f); string_list_clear(&list, 1); return ret; } From 108332c7a04d309cfed872350ab5c8b8e198f944 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:03:49 -0400 Subject: [PATCH 130/539] archive-tar: fix minor indentation violation This looks like a simple omission from 8539070 (archive-tar: unindent write_tar_entry by one level, 2012-05-03). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- archive-tar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archive-tar.c b/archive-tar.c index 0d1e6bd7542dd7..b6b30bb577679f 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -233,7 +233,7 @@ static int write_tar_entry(struct archiver_args *args, size_t rest = pathlen - plen - 1; if (plen > 0 && rest <= sizeof(header.name)) { memcpy(header.prefix, path, plen); - memcpy(header.name, path + plen + 1, rest); + memcpy(header.name, path + plen + 1, rest); } else { sprintf(header.name, "%s.data", sha1_to_hex(sha1)); From fbe85e73ce425b25c15f0b1f3900a9bf895a9793 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:30 -0400 Subject: [PATCH 131/539] fsck: don't fsck alternates for connectivity-only check Commit 02976bf (fsck: introduce `git fsck --connectivity-only`, 2015-06-22) recently gave fsck an option to perform only a subset of the checks, by skipping the fsck_object_dir() call. However, it does so only for the local object directory, and we still do expensive checks on any alternate repos. We should skip them in this case, too. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/fsck.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/builtin/fsck.c b/builtin/fsck.c index 079470342fc926..46c7235180a8dd 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -678,16 +678,17 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) git_config(fsck_config, NULL); fsck_head_link(); - if (!connectivity_only) + if (!connectivity_only) { fsck_object_dir(get_object_directory()); - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); + prepare_alt_odb(); + for (alt = alt_odb_list; alt; alt = alt->next) { + char namebuf[PATH_MAX]; + int namelen = alt->name - alt->base; + memcpy(namebuf, alt->base, namelen); + namebuf[namelen - 1] = 0; + fsck_object_dir(namebuf); + } } if (check_full) { From 7b03c89ebd10396ac7569f0c8c4fa0b4efd4f7ed Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:37 -0400 Subject: [PATCH 132/539] add xsnprintf helper function There are a number of places in the code where we call sprintf(), with the assumption that the output will fit into the buffer. In many cases this is true (e.g., formatting a number into a large buffer), but it is hard to tell immediately from looking at the code. It would be nice if we had some run-time check to make sure that our assumption is correct (and to communicate to readers of the code that we are not blindly calling sprintf, but have actually thought about this case). This patch introduces xsnprintf, which behaves just like snprintf, except that it dies whenever the output is truncated. This acts as a sort of assert() for these cases, which can help find places where the assumption is violated (as opposed to truncating and proceeding, which may just silently give a wrong answer). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-compat-util.h | 3 +++ wrapper.c | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index f649e81f110772..348b9dcc1ccd81 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -744,6 +744,9 @@ static inline size_t xsize_t(off_t len) return (size_t)len; } +__attribute__((format (printf, 3, 4))) +extern int xsnprintf(char *dst, size_t max, const char *fmt, ...); + /* in ctype.c, for kwset users */ extern const unsigned char tolower_trans_tbl[256]; diff --git a/wrapper.c b/wrapper.c index 0e22d4381438b2..6fcaa4dc62b504 100644 --- a/wrapper.c +++ b/wrapper.c @@ -621,6 +621,22 @@ char *xgetcwd(void) return strbuf_detach(&sb, NULL); } +int xsnprintf(char *dst, size_t max, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(dst, max, fmt, ap); + va_end(ap); + + if (len < 0) + die("BUG: your snprintf is broken"); + if (len >= max) + die("BUG: attempt to snprintf into too-small buffer"); + return len; +} + static int write_file_v(const char *path, int fatal, const char *fmt, va_list params) { From bb3788cebb814aa003941abcf484da872aa61412 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:40 -0400 Subject: [PATCH 133/539] add git_path_buf helper function If you have a function that uses git_path a lot, but would prefer to avoid the static buffers, it's useful to keep a single scratch buffer locally and reuse it for each call. You used to be able to do this with git_snpath: char buf[PATH_MAX]; foo(git_snpath(buf, sizeof(buf), "foo")); bar(git_snpath(buf, sizeof(buf), "bar")); but since 1a83c24, git_snpath has been replaced with strbuf_git_path. This is good, because it removes the arbitrary PATH_MAX limit. But using strbuf_git_path is more awkward for two reasons: 1. It adds to the buffer, rather than replacing it. This is consistent with other strbuf functions, but makes reuse of a single buffer more tedious. 2. It doesn't return the buffer, so you can't format as part of a function's arguments. The new git_path_buf solves both of these, so you can use it like: struct strbuf buf = STRBUF_INIT; foo(git_path_buf(&buf, "foo")); bar(git_path_buf(&buf, "bar")); strbuf_release(&buf); Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- cache.h | 2 ++ path.c | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/cache.h b/cache.h index 79066e57dc806d..e231e47121283d 100644 --- a/cache.h +++ b/cache.h @@ -723,6 +723,8 @@ extern char *mksnpath(char *buf, size_t n, const char *fmt, ...) __attribute__((format (printf, 3, 4))); extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path, const char *fmt, ...) __attribute__((format (printf, 3, 4))); diff --git a/path.c b/path.c index 95acbafa6883b4..46a4d2714b98b8 100644 --- a/path.c +++ b/path.c @@ -175,6 +175,16 @@ static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) strbuf_cleanup_path(buf); } +char *git_path_buf(struct strbuf *buf, const char *fmt, ...) +{ + va_list args; + strbuf_reset(buf); + va_start(args, fmt); + do_git_path(buf, fmt, args); + va_end(args); + return buf->buf; +} + void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) { va_list args; From 399ad553ce87fca77a9bc5a0e734a361a9e8a5a3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:43 -0400 Subject: [PATCH 134/539] strbuf: make strbuf_complete_line more generic The strbuf_complete_line function makes sure that a buffer ends in a newline. But we may want to do this for any character (e.g., "/" on the end of a path). Let's factor out a generic version, and keep strbuf_complete_line as a thin wrapper. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- strbuf.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/strbuf.h b/strbuf.h index aef2794651985b..43f27c3a696a6c 100644 --- a/strbuf.h +++ b/strbuf.h @@ -491,10 +491,21 @@ extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char * */ extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s); +/** + * "Complete" the contents of `sb` by ensuring that either it ends with the + * character `term`, or it is empty. This can be used, for example, + * to ensure that text ends with a newline, but without creating an empty + * blank line if there is no content in the first place. + */ +static inline void strbuf_complete(struct strbuf *sb, char term) +{ + if (sb->len && sb->buf[sb->len - 1] != term) + strbuf_addch(sb, term); +} + static inline void strbuf_complete_line(struct strbuf *sb) { - if (sb->len && sb->buf[sb->len - 1] != '\n') - strbuf_addch(sb, '\n'); + strbuf_complete(sb, '\n'); } extern int strbuf_branchname(struct strbuf *sb, const char *name); From af49c6d0918bf04aad89bd885a4eef5767a33d0e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:45 -0400 Subject: [PATCH 135/539] add reentrant variants of sha1_to_hex and find_unique_abbrev The sha1_to_hex and find_unique_abbrev functions always write into reusable static buffers. There are a few problems with this: - future calls overwrite our result. This is especially annoying with find_unique_abbrev, which does not have a ring of buffers, so you cannot even printf() a result that has two abbreviated sha1s. - if you want to put the result into another buffer, we often strcpy, which looks suspicious when auditing for overflows. This patch introduces sha1_to_hex_r and find_unique_abbrev_r, which write into a user-provided buffer. Of course this is just punting on the overflow-auditing, as the buffer obviously needs to be GIT_SHA1_HEXSZ + 1 bytes. But it is much easier to audit, since that is a well-known size. We retain the non-reentrant forms, which just become thin wrappers around the reentrant ones. This patch also adds a strbuf variant of find_unique_abbrev, which will be handy in later patches. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- cache.h | 31 ++++++++++++++++++++++++++++++- hex.c | 13 +++++++++---- sha1_name.c | 16 +++++++++++----- strbuf.c | 9 +++++++++ strbuf.h | 8 ++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/cache.h b/cache.h index e231e47121283d..030b88025705bd 100644 --- a/cache.h +++ b/cache.h @@ -785,7 +785,24 @@ extern char *sha1_pack_name(const unsigned char *sha1); */ extern char *sha1_pack_index_name(const unsigned char *sha1); -extern const char *find_unique_abbrev(const unsigned char *sha1, int); +/* + * Return an abbreviated sha1 unique within this repository's object database. + * The result will be at least `len` characters long, and will be NUL + * terminated. + * + * The non-`_r` version returns a static buffer which will be overwritten by + * subsequent calls. + * + * The `_r` variant writes to a buffer supplied by the caller, which must be at + * least `GIT_SHA1_HEXSZ + 1` bytes. The return value is the number of bytes + * written (excluding the NUL terminator). + * + * Note that while this version avoids the static buffer, it is not fully + * reentrant, as it calls into other non-reentrant git code. + */ +extern const char *find_unique_abbrev(const unsigned char *sha1, int len); +extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len); + extern const unsigned char null_sha1[GIT_SHA1_RAWSZ]; static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) @@ -1067,6 +1084,18 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern int get_oid_hex(const char *hex, struct object_id *sha1); +/* + * Convert a binary sha1 to its hex equivalent. The `_r` variant is reentrant, + * and writes the NUL-terminated output to the buffer `out`, which must be at + * least `GIT_SHA1_HEXSZ + 1` bytes, and returns a pointer to out for + * convenience. + * + * The non-`_r` variant returns a static buffer, but uses a ring of 4 + * buffers, making it safe to make multiple calls for a single statement, like: + * + * printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two)); + */ +extern char *sha1_to_hex_r(char *out, const unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */ diff --git a/hex.c b/hex.c index 899b74a08cb529..0519f853b26e52 100644 --- a/hex.c +++ b/hex.c @@ -61,12 +61,10 @@ int get_oid_hex(const char *hex, struct object_id *oid) return get_sha1_hex(hex, oid->hash); } -char *sha1_to_hex(const unsigned char *sha1) +char *sha1_to_hex_r(char *buffer, const unsigned char *sha1) { - static int bufno; - static char hexbuffer[4][GIT_SHA1_HEXSZ + 1]; static const char hex[] = "0123456789abcdef"; - char *buffer = hexbuffer[3 & ++bufno], *buf = buffer; + char *buf = buffer; int i; for (i = 0; i < GIT_SHA1_RAWSZ; i++) { @@ -79,6 +77,13 @@ char *sha1_to_hex(const unsigned char *sha1) return buffer; } +char *sha1_to_hex(const unsigned char *sha1) +{ + static int bufno; + static char hexbuffer[4][GIT_SHA1_HEXSZ + 1]; + return sha1_to_hex_r(hexbuffer[3 & ++bufno], sha1); +} + char *oid_to_hex(const struct object_id *oid) { return sha1_to_hex(oid->hash); diff --git a/sha1_name.c b/sha1_name.c index da6874c15ec519..c58b4771c0f115 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -368,14 +368,13 @@ int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data) return ds.ambiguous; } -const char *find_unique_abbrev(const unsigned char *sha1, int len) +int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len) { int status, exists; - static char hex[41]; - memcpy(hex, sha1_to_hex(sha1), 40); + sha1_to_hex_r(hex, sha1); if (len == 40 || !len) - return hex; + return 40; exists = has_sha1_file(sha1); while (len < 40) { unsigned char sha1_ret[20]; @@ -384,10 +383,17 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) ? !status : status == SHORT_NAME_NOT_FOUND) { hex[len] = 0; - return hex; + return len; } len++; } + return len; +} + +const char *find_unique_abbrev(const unsigned char *sha1, int len) +{ + static char hex[GIT_SHA1_HEXSZ + 1]; + find_unique_abbrev_r(hex, sha1, len); return hex; } diff --git a/strbuf.c b/strbuf.c index 29df55b1b0f365..f3c44fb3b33b14 100644 --- a/strbuf.c +++ b/strbuf.c @@ -743,3 +743,12 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm) } strbuf_setlen(sb, sb->len + len); } + +void strbuf_add_unique_abbrev(struct strbuf *sb, const unsigned char *sha1, + int abbrev_len) +{ + int r; + strbuf_grow(sb, GIT_SHA1_HEXSZ + 1); + r = find_unique_abbrev_r(sb->buf + sb->len, sha1, abbrev_len); + strbuf_setlen(sb, sb->len + r); +} diff --git a/strbuf.h b/strbuf.h index 43f27c3a696a6c..0f9c8a72ba7cdb 100644 --- a/strbuf.h +++ b/strbuf.h @@ -474,6 +474,14 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb, */ extern void strbuf_list_free(struct strbuf **); +/** + * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to + * the strbuf `sb`. + */ +extern void strbuf_add_unique_abbrev(struct strbuf *sb, + const unsigned char *sha1, + int abbrev_len); + /** * Launch the user preferred editor to edit a file and fill the buffer * with the file's contents upon the user completing their editing. The From c1fd0809174a31edd17e97c1161e01907f41a4fc Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:48 -0400 Subject: [PATCH 136/539] fsck: use strbuf to generate alternate directories When fsck-ing alternates, we make a copy of the alternate directory in a fixed PATH_MAX buffer. We memcpy directly, without any check whether we are overflowing the buffer. This is OK if PATH_MAX is a true representation of the maximum path on the system, because any path here will have already been vetted by the alternates subsystem. But that is not true on every system, so we should be more careful. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/fsck.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/builtin/fsck.c b/builtin/fsck.c index 46c7235180a8dd..a019f4a2408ab4 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -683,11 +683,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); + /* directory name, minus trailing slash */ + size_t namelen = alt->name - alt->base - 1; + struct strbuf name = STRBUF_INIT; + strbuf_add(&name, alt->base, namelen); + fsck_object_dir(name.buf); + strbuf_release(&name); } } From 1d895f194ff612057989f477dc106aa1c7ac2016 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:51 -0400 Subject: [PATCH 137/539] mailsplit: make PATH_MAX buffers dynamic There are several PATH_MAX-sized buffers in mailsplit, along with some questionable uses of sprintf. These are not really of security interest, as local mailsplit pathnames are not typically under control of an attacker, and you could generally only overflow a few numbers at the end of a path that approaches PATH_MAX (a longer path would choke mailsplit long before). But it does not hurt to be careful, and as a bonus we lift some limits for systems with too-small PATH_MAX varibles. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/mailsplit.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 9de06e3cf7f9d1..104277acc49a08 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -98,30 +98,37 @@ static int populate_maildir_list(struct string_list *list, const char *path) { DIR *dir; struct dirent *dent; - char name[PATH_MAX]; + char *name = NULL; char *subs[] = { "cur", "new", NULL }; char **sub; + int ret = -1; for (sub = subs; *sub; ++sub) { - snprintf(name, sizeof(name), "%s/%s", path, *sub); + free(name); + name = xstrfmt("%s/%s", path, *sub); if ((dir = opendir(name)) == NULL) { if (errno == ENOENT) continue; error("cannot opendir %s (%s)", name, strerror(errno)); - return -1; + goto out; } while ((dent = readdir(dir)) != NULL) { if (dent->d_name[0] == '.') continue; - snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name); + free(name); + name = xstrfmt("%s/%s", *sub, dent->d_name); string_list_insert(list, name); } closedir(dir); } - return 0; + ret = 0; + +out: + free(name); + return ret; } static int maildir_filename_cmp(const char *a, const char *b) @@ -148,8 +155,7 @@ static int maildir_filename_cmp(const char *a, const char *b) static int split_maildir(const char *maildir, const char *dir, int nr_prec, int skip) { - char file[PATH_MAX]; - char name[PATH_MAX]; + char *file = NULL; FILE *f = NULL; int ret = -1; int i; @@ -161,7 +167,11 @@ static int split_maildir(const char *maildir, const char *dir, goto out; for (i = 0; i < list.nr; i++) { - snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string); + char *name; + + free(file); + file = xstrfmt("%s/%s", maildir, list.items[i].string); + f = fopen(file, "r"); if (!f) { error("cannot open mail %s (%s)", file, strerror(errno)); @@ -173,8 +183,9 @@ static int split_maildir(const char *maildir, const char *dir, goto out; } - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); + name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); split_one(f, name, 1); + free(name); fclose(f); f = NULL; @@ -184,6 +195,7 @@ static int split_maildir(const char *maildir, const char *dir, out: if (f) fclose(f); + free(file); string_list_clear(&list, 1); return ret; } @@ -191,7 +203,6 @@ static int split_maildir(const char *maildir, const char *dir, static int split_mbox(const char *file, const char *dir, int allow_bare, int nr_prec, int skip) { - char name[PATH_MAX]; int ret = -1; int peek; @@ -218,8 +229,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, } while (!file_done) { - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); + char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); file_done = split_one(f, name, allow_bare); + free(name); } if (f != stdin) From 0bb443fdd2518600f88434e2aad8f515546ee707 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:54 -0400 Subject: [PATCH 138/539] trace: use strbuf for quote_crnl output When we output GIT_TRACE_SETUP paths, we quote any meta-characters. But our buffer to hold the result is only PATH_MAX bytes, and we could double the size of the input path (if every character needs quoting). We could use a 2*PATH_MAX buffer, if we assume the input will never be more than PATH_MAX. But it's easier still to just switch to a strbuf and not worry about whether the input can exceed PATH_MAX or not. The original copied the "p2" pointer to "p1", advancing both. Since this gets rid of "p1", let's also drop "p2", whose name is now confusing. We can just advance the original "path" pointer. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- trace.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/trace.c b/trace.c index 7393926ebcd954..4aeea60973100b 100644 --- a/trace.c +++ b/trace.c @@ -277,25 +277,24 @@ void trace_performance_fl(const char *file, int line, uint64_t nanos, static const char *quote_crnl(const char *path) { - static char new_path[PATH_MAX]; - const char *p2 = path; - char *p1 = new_path; + static struct strbuf new_path = STRBUF_INIT; if (!path) return NULL; - while (*p2) { - switch (*p2) { - case '\\': *p1++ = '\\'; *p1++ = '\\'; break; - case '\n': *p1++ = '\\'; *p1++ = 'n'; break; - case '\r': *p1++ = '\\'; *p1++ = 'r'; break; + strbuf_reset(&new_path); + + while (*path) { + switch (*path) { + case '\\': strbuf_addstr(&new_path, "\\\\"); break; + case '\n': strbuf_addstr(&new_path, "\\n"); break; + case '\r': strbuf_addstr(&new_path, "\\r"); break; default: - *p1++ = *p2; + strbuf_addch(&new_path, *path); } - p2++; + path++; } - *p1 = '\0'; - return new_path; + return new_path.buf; } /* FIXME: move prefix to startup_info struct and get rid of this arg */ From 3131977de1476185209d4d56a0494f5b30ee5557 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:05:57 -0400 Subject: [PATCH 139/539] progress: store throughput display in a strbuf Coverity noticed that we strncpy() into a fixed-size buffer without making sure that it actually ended up NUL-terminated. This is unlikely to be a bug in practice, since throughput strings rarely hit 32 characters, but it would be nice to clean it up. The most obvious way to do so is to add a NUL-terminator. But instead, this patch switches the fixed-size buffer out for a strbuf. At first glance this seems much less efficient, until we realize that filling in the fixed-size buffer is done by writing into a strbuf and copying the result! By writing straight to the buffer, we actually end up more efficient: 1. We avoid an extra copy of the bytes. 2. Rather than malloc/free each time progress is shown, we can strbuf_reset and use the same buffer each time. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- progress.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/progress.c b/progress.c index 2e31bec60f5c98..a3efcfd34ccf60 100644 --- a/progress.c +++ b/progress.c @@ -25,7 +25,7 @@ struct throughput { unsigned int last_bytes[TP_IDX_MAX]; unsigned int last_misecs[TP_IDX_MAX]; unsigned int idx; - char display[32]; + struct strbuf display; }; struct progress { @@ -98,7 +98,7 @@ static int display(struct progress *progress, unsigned n, const char *done) } progress->last_value = n; - tp = (progress->throughput) ? progress->throughput->display : ""; + tp = (progress->throughput) ? progress->throughput->display.buf : ""; eol = done ? done : " \r"; if (progress->total) { unsigned percent = n * 100 / progress->total; @@ -129,6 +129,7 @@ static int display(struct progress *progress, unsigned n, const char *done) static void throughput_string(struct strbuf *buf, off_t total, unsigned int rate) { + strbuf_reset(buf); strbuf_addstr(buf, ", "); strbuf_humanise_bytes(buf, total); strbuf_addstr(buf, " | "); @@ -141,7 +142,6 @@ void display_throughput(struct progress *progress, off_t total) struct throughput *tp; uint64_t now_ns; unsigned int misecs, count, rate; - struct strbuf buf = STRBUF_INIT; if (!progress) return; @@ -154,6 +154,7 @@ void display_throughput(struct progress *progress, off_t total) if (tp) { tp->prev_total = tp->curr_total = total; tp->prev_ns = now_ns; + strbuf_init(&tp->display, 0); } return; } @@ -193,9 +194,7 @@ void display_throughput(struct progress *progress, off_t total) tp->last_misecs[tp->idx] = misecs; tp->idx = (tp->idx + 1) % TP_IDX_MAX; - throughput_string(&buf, total, rate); - strncpy(tp->display, buf.buf, sizeof(tp->display)); - strbuf_release(&buf); + throughput_string(&tp->display, total, rate); if (progress->last_value != -1 && progress_update) display(progress, progress->last_value, NULL); } @@ -250,12 +249,9 @@ void stop_progress_msg(struct progress **p_progress, const char *msg) bufp = (len < sizeof(buf)) ? buf : xmalloc(len + 1); if (tp) { - struct strbuf strbuf = STRBUF_INIT; unsigned int rate = !tp->avg_misecs ? 0 : tp->avg_bytes / tp->avg_misecs; - throughput_string(&strbuf, tp->curr_total, rate); - strncpy(tp->display, strbuf.buf, sizeof(tp->display)); - strbuf_release(&strbuf); + throughput_string(&tp->display, tp->curr_total, rate); } progress_update = 1; sprintf(bufp, ", %s.\n", msg); @@ -264,6 +260,8 @@ void stop_progress_msg(struct progress **p_progress, const char *msg) free(bufp); } clear_progress_signal(); + if (progress->throughput) + strbuf_release(&progress->throughput->display); free(progress->throughput); free(progress); } From 04724222d5aad4da48674e69aab715273f05dded Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:03 -0400 Subject: [PATCH 140/539] test-dump-cache-tree: avoid overflow of cache-tree name When dumping a cache-tree, we sprintf sub-tree names directly into a fixed-size buffer, which can overflow. We can trivially fix this by converting to xsnprintf to at least notice and die. This probably should handle arbitrary-sized names, but there's not much point. It's used only by the test scripts, so the trivial fix is enough. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- test-dump-cache-tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c index 54c0872fcb18ed..bb53c0aa655c7a 100644 --- a/test-dump-cache-tree.c +++ b/test-dump-cache-tree.c @@ -47,7 +47,7 @@ static int dump_cache_tree(struct cache_tree *it, struct cache_tree_sub *rdwn; rdwn = cache_tree_sub(ref, down->name); - sprintf(path, "%s%.*s/", pfx, down->namelen, down->name); + xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name); if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path)) errs = 1; } From db85a8a9c2fb492d3cd528dbbcc52075c607cf79 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:06 -0400 Subject: [PATCH 141/539] compat/inet_ntop: fix off-by-one in inet_ntop4 Our compat inet_ntop4 function writes to a temporary buffer with snprintf, and then uses strcpy to put the result into the final "dst" buffer. We check the return value of snprintf against the size of "dst", but fail to account for the NUL terminator. As a result, we may overflow "dst" with a single NUL. In practice, this doesn't happen because the output of inet_ntop is limited, and we provide buffers that are way oversized. We can fix the off-by-one check easily, but while we are here let's also use strlcpy for increased safety, just in case there are other bugs lurking. As a side note, this compat code seems to be BSD-derived. Searching for "vixie inet_ntop" turns up NetBSD's latest version of the same code, which has an identical fix (and switches to strlcpy, too!). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- compat/inet_ntop.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c index 90b7cc45f33411..68307262be0495 100644 --- a/compat/inet_ntop.c +++ b/compat/inet_ntop.c @@ -53,11 +53,11 @@ inet_ntop4(const u_char *src, char *dst, size_t size) nprinted = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]); if (nprinted < 0) return (NULL); /* we assume "errno" was set by "snprintf()" */ - if ((size_t)nprinted > size) { + if ((size_t)nprinted >= size) { errno = ENOSPC; return (NULL); } - strcpy(dst, tmp); + strlcpy(dst, tmp, size); return (dst); } @@ -154,7 +154,7 @@ inet_ntop6(const u_char *src, char *dst, size_t size) errno = ENOSPC; return (NULL); } - strcpy(dst, tmp); + strlcpy(dst, tmp, size); return (dst); } #endif From 5096d4909f9b13c7a650d9dbb7c9702ea7413566 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:08 -0400 Subject: [PATCH 142/539] convert trivial sprintf / strcpy calls to xsnprintf We sometimes sprintf into fixed-size buffers when we know that the buffer is large enough to fit the input (either because it's a constant, or because it's numeric input that is bounded in size). Likewise with strcpy of constant strings. However, these sites make it hard to audit sprintf and strcpy calls for buffer overflows, as a reader has to cross-reference the size of the array with the input. Let's use xsnprintf instead, which communicates to a reader that we don't expect this to overflow (and catches the mistake in case we do). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- archive-tar.c | 2 +- builtin/gc.c | 2 +- builtin/init-db.c | 11 ++++++----- builtin/ls-tree.c | 9 +++++---- builtin/merge-index.c | 2 +- builtin/merge-recursive.c | 2 +- builtin/read-tree.c | 2 +- builtin/unpack-file.c | 2 +- compat/mingw.c | 8 +++++--- compat/winansi.c | 2 +- connect.c | 2 +- convert.c | 3 ++- daemon.c | 4 ++-- diff.c | 12 ++++++------ http-push.c | 2 +- http.c | 6 +++--- ll-merge.c | 12 ++++++------ refs.c | 8 ++++---- sideband.c | 4 ++-- strbuf.c | 4 ++-- 20 files changed, 52 insertions(+), 47 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index b6b30bb577679f..d543f93fc9f70e 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -301,7 +301,7 @@ static int write_global_extended_header(struct archiver_args *args) memset(&header, 0, sizeof(header)); *header.typeflag = TYPEFLAG_GLOBAL_HEADER; mode = 0100666; - strcpy(header.name, "pax_global_header"); + xsnprintf(header.name, sizeof(header.name), "pax_global_header"); prepare_header(args, &header, mode, ext_header.len); write_blocked(&header, sizeof(header)); write_blocked(ext_header.buf, ext_header.len); diff --git a/builtin/gc.c b/builtin/gc.c index 0ad8d30b56f89a..57584bc99faf49 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -194,7 +194,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) return NULL; if (gethostname(my_host, sizeof(my_host))) - strcpy(my_host, "unknown"); + xsnprintf(my_host, sizeof(my_host), "unknown"); pidfile_path = git_pathdup("gc.pid"); fd = hold_lock_file_for_update(&lock, pidfile_path, diff --git a/builtin/init-db.c b/builtin/init-db.c index 69323e186cda73..e7d0e31f46e989 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -262,7 +262,8 @@ static int create_default_files(const char *template_path) } /* This forces creation of new config file */ - sprintf(repo_version_string, "%d", GIT_REPO_VERSION); + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", GIT_REPO_VERSION); git_config_set("core.repositoryformatversion", repo_version_string); path[len] = 0; @@ -414,13 +415,13 @@ int init_db(const char *template_dir, unsigned int flags) */ if (shared_repository < 0) /* force to the mode value */ - sprintf(buf, "0%o", -shared_repository); + xsnprintf(buf, sizeof(buf), "0%o", -shared_repository); else if (shared_repository == PERM_GROUP) - sprintf(buf, "%d", OLD_PERM_GROUP); + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); else if (shared_repository == PERM_EVERYBODY) - sprintf(buf, "%d", OLD_PERM_EVERYBODY); + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else - die("oops"); + die("BUG: invalid value for shared_repository"); git_config_set("core.sharedrepository", buf); git_config_set("receive.denyNonFastforwards", "true"); } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 3b04a0f082a48c..0e30d862303b67 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -96,12 +96,13 @@ static int show_tree(const unsigned char *sha1, struct strbuf *base, if (!strcmp(type, blob_type)) { unsigned long size; if (sha1_object_info(sha1, &size) == OBJ_BAD) - strcpy(size_text, "BAD"); + xsnprintf(size_text, sizeof(size_text), + "BAD"); else - snprintf(size_text, sizeof(size_text), - "%lu", size); + xsnprintf(size_text, sizeof(size_text), + "%lu", size); } else - strcpy(size_text, "-"); + xsnprintf(size_text, sizeof(size_text), "-"); printf("%06o %s %s %7s\t", mode, type, find_unique_abbrev(sha1, abbrev), size_text); diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 1a1eafa6fdc2e9..1d6611191791ff 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -23,7 +23,7 @@ static int merge_entry(int pos, const char *path) break; found++; strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ce->ce_mode); + xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; } while (++pos < active_nr); diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index a90f28f34d2072..491efd556e87d0 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -14,7 +14,7 @@ static const char *better_branch_name(const char *branch) if (strlen(branch) != 40) return branch; - sprintf(githead_env, "GITHEAD_%s", branch); + xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch); name = getenv(githead_env); return name ? name : branch; } diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 2379e11069ef22..8c693e756852ef 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -90,7 +90,7 @@ static int debug_merge(const struct cache_entry * const *stages, debug_stage("index", stages[0], o); for (i = 1; i <= o->merge_size; i++) { char buf[24]; - sprintf(buf, "ent#%d", i); + xsnprintf(buf, sizeof(buf), "ent#%d", i); debug_stage(buf, stages[i], o); } return 0; diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 19200291a26325..6fc6bcdf7f014a 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -12,7 +12,7 @@ static char *create_temp_file(unsigned char *sha1) if (!buf || type != OBJ_BLOB) die("unable to read blob object %s", sha1_to_hex(sha1)); - strcpy(path, ".merge_file_XXXXXX"); + xsnprintf(path, sizeof(path), ".merge_file_XXXXXX"); fd = xmkstemp(path); if (write_in_full(fd, buf, size) != size) die_errno("unable to write temp-file"); diff --git a/compat/mingw.c b/compat/mingw.c index f74da235f598d8..a168800ae0d1a6 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2133,9 +2133,11 @@ int uname(struct utsname *buf) { DWORD v = GetVersion(); memset(buf, 0, sizeof(*buf)); - strcpy(buf->sysname, "Windows"); - sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff); + xsnprintf(buf->sysname, sizeof(buf->sysname), "Windows"); + xsnprintf(buf->release, sizeof(buf->release), + "%u.%u", v & 0xff, (v >> 8) & 0xff); /* assuming NT variants only.. */ - sprintf(buf->version, "%u", (v >> 16) & 0x7fff); + xsnprintf(buf->version, sizeof(buf->version), + "%u", (v >> 16) & 0x7fff); return 0; } diff --git a/compat/winansi.c b/compat/winansi.c index efc5bb3a4b6316..ceff55bd676964 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -539,7 +539,7 @@ void winansi_init(void) return; /* create a named pipe to communicate with the console thread */ - sprintf(name, "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()); + xsnprintf(name, sizeof(name), "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()); hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL); if (hwrite == INVALID_HANDLE_VALUE) diff --git a/connect.c b/connect.c index c0144d859ae427..1d5c5e005557ae 100644 --- a/connect.c +++ b/connect.c @@ -332,7 +332,7 @@ static const char *ai_name(const struct addrinfo *ai) static char addr[NI_MAXHOST]; if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0, NI_NUMERICHOST) != 0) - strcpy(addr, "(unknown)"); + xsnprintf(addr, sizeof(addr), "(unknown)"); return addr; } diff --git a/convert.c b/convert.c index f3bd3e93fb2ecf..814e814438b7c0 100644 --- a/convert.c +++ b/convert.c @@ -1289,7 +1289,8 @@ static struct stream_filter *ident_filter(const unsigned char *sha1) { struct ident_filter *ident = xmalloc(sizeof(*ident)); - sprintf(ident->ident, ": %s $", sha1_to_hex(sha1)); + xsnprintf(ident->ident, sizeof(ident->ident), + ": %s $", sha1_to_hex(sha1)); strbuf_init(&ident->left, 0); ident->filter.vtbl = &ident_vtbl; ident->state = 0; diff --git a/daemon.c b/daemon.c index f9eb296888c37c..5218a3f1ccc532 100644 --- a/daemon.c +++ b/daemon.c @@ -901,7 +901,7 @@ static const char *ip2str(int family, struct sockaddr *sin, socklen_t len) inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len); break; default: - strcpy(ip, ""); + xsnprintf(ip, sizeof(ip), ""); } return ip; } @@ -916,7 +916,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis int gai; long flags; - sprintf(pbuf, "%d", listen_port); + xsnprintf(pbuf, sizeof(pbuf), "%d", listen_port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; diff --git a/diff.c b/diff.c index 08508f6a2017eb..788e371decee39 100644 --- a/diff.c +++ b/diff.c @@ -2880,7 +2880,7 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, temp->name = get_tempfile_path(&temp->tempfile); strcpy(temp->hex, sha1_to_hex(sha1)); temp->hex[40] = 0; - sprintf(temp->mode, "%06o", mode); + xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode); strbuf_release(&buf); strbuf_release(&template); free(path_dup); @@ -2897,8 +2897,8 @@ static struct diff_tempfile *prepare_temp_file(const char *name, * a '+' entry produces this for file-1. */ temp->name = "/dev/null"; - strcpy(temp->hex, "."); - strcpy(temp->mode, "."); + xsnprintf(temp->hex, sizeof(temp->hex), "."); + xsnprintf(temp->mode, sizeof(temp->mode), "."); return temp; } @@ -2935,7 +2935,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name, * !(one->sha1_valid), as long as * DIFF_FILE_VALID(one). */ - sprintf(temp->mode, "%06o", one->mode); + xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode); } return temp; } @@ -4081,9 +4081,9 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len) if (abblen < 37) { static char hex[41]; if (len < abblen && abblen <= len + 2) - sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); + xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, ".."); else - sprintf(hex, "%s...", abbrev); + xsnprintf(hex, sizeof(hex), "%s...", abbrev); return hex; } return sha1_to_hex(sha1); diff --git a/http-push.c b/http-push.c index c98dad23dfd868..154e67bb05f0cc 100644 --- a/http-push.c +++ b/http-push.c @@ -881,7 +881,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped); free(escaped); - sprintf(timeout_header, "Timeout: Second-%ld", timeout); + xsnprintf(timeout_header, sizeof(timeout_header), "Timeout: Second-%ld", timeout); dav_headers = curl_slist_append(dav_headers, timeout_header); dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); diff --git a/http.c b/http.c index 9dce38025c7b35..7b0225996189fc 100644 --- a/http.c +++ b/http.c @@ -1104,7 +1104,7 @@ static void write_accept_language(struct strbuf *buf) decimal_places++, max_q *= 10) ; - sprintf(q_format, ";q=0.%%0%dd", decimal_places); + xsnprintf(q_format, sizeof(q_format), ";q=0.%%0%dd", decimal_places); strbuf_addstr(buf, "Accept-Language: "); @@ -1601,7 +1601,7 @@ struct http_pack_request *new_http_pack_request( fprintf(stderr, "Resuming fetch of pack %s at byte %ld\n", sha1_to_hex(target->sha1), prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); + xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn); preq->range_header = curl_slist_append(NULL, range); curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, preq->range_header); @@ -1761,7 +1761,7 @@ struct http_object_request *new_http_object_request(const char *base_url, fprintf(stderr, "Resuming fetch of object %s at byte %ld\n", hex, prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); + xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn); range_header = curl_slist_append(range_header, range); curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, range_header); diff --git a/ll-merge.c b/ll-merge.c index fc3c0495942e2a..56f73b39325609 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -142,11 +142,11 @@ static struct ll_merge_driver ll_merge_drv[] = { { "union", "built-in union merge", ll_union_merge }, }; -static void create_temp(mmfile_t *src, char *path) +static void create_temp(mmfile_t *src, char *path, size_t len) { int fd; - strcpy(path, ".merge_file_XXXXXX"); + xsnprintf(path, len, ".merge_file_XXXXXX"); fd = xmkstemp(path); if (write_in_full(fd, src->ptr, src->size) != src->size) die_errno("unable to write temp-file"); @@ -187,10 +187,10 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, result->ptr = NULL; result->size = 0; - create_temp(orig, temp[0]); - create_temp(src1, temp[1]); - create_temp(src2, temp[2]); - sprintf(temp[3], "%d", marker_size); + create_temp(orig, temp[0], sizeof(temp[0])); + create_temp(src1, temp[1], sizeof(temp[1])); + create_temp(src2, temp[2], sizeof(temp[2])); + xsnprintf(temp[3], sizeof(temp[3]), "%d", marker_size); strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict); diff --git a/refs.c b/refs.c index 4e15f60d98ea8a..d5c8b2f51b8ad6 100644 --- a/refs.c +++ b/refs.c @@ -3326,10 +3326,10 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1, msglen = msg ? strlen(msg) : 0; maxlen = strlen(committer) + msglen + 100; logrec = xmalloc(maxlen); - len = sprintf(logrec, "%s %s %s\n", - sha1_to_hex(old_sha1), - sha1_to_hex(new_sha1), - committer); + len = xsnprintf(logrec, maxlen, "%s %s %s\n", + sha1_to_hex(old_sha1), + sha1_to_hex(new_sha1), + committer); if (msglen) len += copy_msg(logrec + len - 1, msg) - 1; diff --git a/sideband.c b/sideband.c index 7f9dc229fbc82d..fde8adc000f316 100644 --- a/sideband.c +++ b/sideband.c @@ -137,11 +137,11 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet if (packet_max - 5 < n) n = packet_max - 5; if (0 <= band) { - sprintf(hdr, "%04x", n + 5); + xsnprintf(hdr, sizeof(hdr), "%04x", n + 5); hdr[4] = band; write_or_die(fd, hdr, 5); } else { - sprintf(hdr, "%04x", n + 4); + xsnprintf(hdr, sizeof(hdr), "%04x", n + 4); write_or_die(fd, hdr, 4); } write_or_die(fd, p, n); diff --git a/strbuf.c b/strbuf.c index f3c44fb3b33b14..107c45d29111a0 100644 --- a/strbuf.c +++ b/strbuf.c @@ -245,8 +245,8 @@ void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size static char prefix2[2]; if (prefix1[0] != comment_line_char) { - sprintf(prefix1, "%c ", comment_line_char); - sprintf(prefix2, "%c", comment_line_char); + xsnprintf(prefix1, sizeof(prefix1), "%c ", comment_line_char); + xsnprintf(prefix2, sizeof(prefix2), "%c", comment_line_char); } add_lines(out, prefix1, prefix2, buf, size); } From f2f026752993054c1b712b6f4ae3ff167db38cbe Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:24 -0400 Subject: [PATCH 143/539] archive-tar: use xsnprintf for trivial formatting When we generate tar headers, we sprintf() values directly into a struct with the fixed-size header values. For the most part this is fine, as we are formatting small values (e.g., the octal format of "mode & 0x7777" is of fixed length). But it's still a good idea to use xsnprintf here. It communicates to readers what our expectation is, and it provides a run-time check that we are not overflowing the buffers. The one exception here is the mtime, which comes from the epoch time of the commit we are archiving. For sane values, this fits into the 12-byte value allocated in the header. But since git can handle 64-bit times, if I claim to be a visitor from the year 10,000 AD, I can overflow the buffer. This turns out to be harmless, as we simply overflow into the chksum field, which is then overwritten. This case is also best as an xsnprintf. It should never come up, short of extremely malformed dates, and in that case we are probably better off dying than silently truncating the date value (and we cannot expand the size of the buffer, since it is dictated by the ustar format). Our friends in the year 5138 (when we legitimately flip to a 12-digit epoch) can deal with that problem then. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- archive-tar.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index d543f93fc9f70e..501ca97760b1f6 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -167,21 +167,21 @@ static void prepare_header(struct archiver_args *args, struct ustar_header *header, unsigned int mode, unsigned long size) { - sprintf(header->mode, "%07o", mode & 07777); - sprintf(header->size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header->mtime, "%011lo", (unsigned long) args->time); + xsnprintf(header->mode, sizeof(header->mode), "%07o", mode & 07777); + xsnprintf(header->size, sizeof(header->size), "%011lo", S_ISREG(mode) ? size : 0); + xsnprintf(header->mtime, sizeof(header->mtime), "%011lo", (unsigned long) args->time); - sprintf(header->uid, "%07o", 0); - sprintf(header->gid, "%07o", 0); + xsnprintf(header->uid, sizeof(header->uid), "%07o", 0); + xsnprintf(header->gid, sizeof(header->gid), "%07o", 0); strlcpy(header->uname, "root", sizeof(header->uname)); strlcpy(header->gname, "root", sizeof(header->gname)); - sprintf(header->devmajor, "%07o", 0); - sprintf(header->devminor, "%07o", 0); + xsnprintf(header->devmajor, sizeof(header->devmajor), "%07o", 0); + xsnprintf(header->devminor, sizeof(header->devminor), "%07o", 0); memcpy(header->magic, "ustar", 6); memcpy(header->version, "00", 2); - sprintf(header->chksum, "%07o", ustar_header_chksum(header)); + snprintf(header->chksum, sizeof(header->chksum), "%07o", ustar_header_chksum(header)); } static int write_extended_header(struct archiver_args *args, @@ -193,7 +193,7 @@ static int write_extended_header(struct archiver_args *args, memset(&header, 0, sizeof(header)); *header.typeflag = TYPEFLAG_EXT_HEADER; mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + xsnprintf(header.name, sizeof(header.name), "%s.paxheader", sha1_to_hex(sha1)); prepare_header(args, &header, mode, size); write_blocked(&header, sizeof(header)); write_blocked(buffer, size); @@ -235,8 +235,8 @@ static int write_tar_entry(struct archiver_args *args, memcpy(header.prefix, path, plen); memcpy(header.name, path + plen + 1, rest); } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); + xsnprintf(header.name, sizeof(header.name), "%s.data", + sha1_to_hex(sha1)); strbuf_append_ext_header(&ext_header, "path", path, pathlen); } @@ -259,8 +259,8 @@ static int write_tar_entry(struct archiver_args *args, if (S_ISLNK(mode)) { if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); + xsnprintf(header.linkname, sizeof(header.linkname), + "see %s.paxheader", sha1_to_hex(sha1)); strbuf_append_ext_header(&ext_header, "linkpath", buffer, size); } else From ef1286d3c0ba714c6c2ae87e14edf3c462aef114 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:42 -0400 Subject: [PATCH 144/539] use xsnprintf for generating git object headers We generally use 32-byte buffers to format git's "type size" header fields. These should not generally overflow unless you can produce some truly gigantic objects (and our types come from our internal array of constant strings). But it is a good idea to use xsnprintf to make sure this is the case. Note that we slightly modify the interface to write_sha1_file_prepare, which nows uses "hdrlen" as an "in" parameter as well as an "out" (on the way in it stores the allocated size of the header, and on the way out it returns the ultimate size of the header). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/index-pack.c | 2 +- bulk-checkin.c | 4 ++-- fast-import.c | 4 ++-- http-push.c | 2 +- sha1_file.c | 13 +++++++------ 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 3431de2362d981..1ad1bde69600d3 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -441,7 +441,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, int hdrlen; if (!is_delta_type(type)) { - hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1; git_SHA1_Init(&c); git_SHA1_Update(&c, hdr, hdrlen); } else diff --git a/bulk-checkin.c b/bulk-checkin.c index 7cffc3a579e4a0..4347f5c76aa728 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -200,8 +200,8 @@ static int deflate_to_pack(struct bulk_checkin_state *state, if (seekback == (off_t) -1) return error("cannot find the current offset"); - header_len = sprintf((char *)obuf, "%s %" PRIuMAX, - typename(type), (uintmax_t)size) + 1; + header_len = xsnprintf((char *)obuf, sizeof(obuf), "%s %" PRIuMAX, + typename(type), (uintmax_t)size) + 1; git_SHA1_Init(&ctx); git_SHA1_Update(&ctx, obuf, header_len); diff --git a/fast-import.c b/fast-import.c index 6c7c3c9b669eb2..d0c25024cd4257 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1035,8 +1035,8 @@ static int store_object( git_SHA_CTX c; git_zstream s; - hdrlen = sprintf((char *)hdr,"%s %lu", typename(type), - (unsigned long)dat->len) + 1; + hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu", + typename(type), (unsigned long)dat->len) + 1; git_SHA1_Init(&c); git_SHA1_Update(&c, hdr, hdrlen); git_SHA1_Update(&c, dat->buf, dat->len); diff --git a/http-push.c b/http-push.c index 154e67bb05f0cc..1f3788f634ba29 100644 --- a/http-push.c +++ b/http-push.c @@ -361,7 +361,7 @@ static void start_put(struct transfer_request *request) git_zstream stream; unpacked = read_sha1_file(request->obj->sha1, &type, &len); - hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1; /* Set it up */ git_deflate_init(&stream, zlib_compression_level); diff --git a/sha1_file.c b/sha1_file.c index d295a3225a1e08..f1060914745bb0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1464,7 +1464,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map, return -1; /* Generate the header */ - hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(obj_type), size) + 1; /* Sha1.. */ git_SHA1_Init(&c); @@ -2930,7 +2930,7 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len, git_SHA_CTX c; /* Generate the header */ - *hdrlen = sprintf(hdr, "%s %lu", type, len)+1; + *hdrlen = xsnprintf(hdr, *hdrlen, "%s %lu", type, len)+1; /* Sha1.. */ git_SHA1_Init(&c); @@ -2993,7 +2993,7 @@ int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1) { char hdr[32]; - int hdrlen; + int hdrlen = sizeof(hdr); write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); return 0; } @@ -3139,7 +3139,7 @@ static int freshen_packed_object(const unsigned char *sha1) int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1) { char hdr[32]; - int hdrlen; + int hdrlen = sizeof(hdr); /* Normally if we have it in the pack then we do not bother writing * it out into .git/objects/??/?{38} file. @@ -3157,7 +3157,8 @@ int hash_sha1_file_literally(const void *buf, unsigned long len, const char *typ int hdrlen, status = 0; /* type string, SP, %lu of the length plus NUL must fit this */ - header = xmalloc(strlen(type) + 32); + hdrlen = strlen(type) + 32; + header = xmalloc(hdrlen); write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen); if (!(flags & HASH_WRITE_OBJECT)) @@ -3185,7 +3186,7 @@ int force_object_loose(const unsigned char *sha1, time_t mtime) buf = read_packed_sha1(sha1, &type, &len); if (!buf) return error("cannot read sha1_file for %s", sha1_to_hex(sha1)); - hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1; ret = write_loose_object(sha1, hdr, hdrlen, buf, len, mtime); free(buf); From c3bb0ac796c21490f478914441526817e4685606 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:44 -0400 Subject: [PATCH 145/539] find_short_object_filename: convert sprintf to xsnprintf We use sprintf() to format some hex data into a buffer. The buffer is clearly long enough, and using snprintf here is not necessary. And in fact, it does not really make anything easier to audit, as the size we feed to snprintf accounts for the magic extra 42 bytes found in each alt->name field of struct alternate_object_database (which is there exactly to do this formatting). Still, it is nice to remove an sprintf call and replace it with an xsnprintf and explanatory comment, which makes it easier to audit the code base for overflows. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- sha1_name.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index c58b4771c0f115..80753b67704327 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -96,11 +96,15 @@ static void find_short_object_filename(int len, const char *hex_pfx, struct disa } fakeent->next = alt_odb_list; - sprintf(hex, "%.2s", hex_pfx); + xsnprintf(hex, sizeof(hex), "%.2s", hex_pfx); for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) { struct dirent *de; DIR *dir; - sprintf(alt->name, "%.2s/", hex_pfx); + /* + * every alt_odb struct has 42 extra bytes after the base + * for exactly this purpose + */ + xsnprintf(alt->name, 42, "%.2s/", hex_pfx); dir = opendir(alt->base); if (!dir) continue; From f5691aa640ae2c1c5f85037e093de7f310c0eac7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:46 -0400 Subject: [PATCH 146/539] stop_progress_msg: convert sprintf to xsnprintf The usual arguments for using xsnprintf over sprintf apply, but this case is a little tricky. We print to a fixed-size buffer if we have room, and otherwise to an allocated buffer. So there should be no overflow here, but it is still good to communicate our intention, as well as to check our earlier math for how much space the string will need. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- progress.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progress.c b/progress.c index a3efcfd34ccf60..353bd37416e65f 100644 --- a/progress.c +++ b/progress.c @@ -254,7 +254,7 @@ void stop_progress_msg(struct progress **p_progress, const char *msg) throughput_string(&tp->display, tp->curr_total, rate); } progress_update = 1; - sprintf(bufp, ", %s.\n", msg); + xsnprintf(bufp, len + 1, ", %s.\n", msg); display(progress, progress->last_value, bufp); if (buf != bufp) free(bufp); From 48bdf86995423736f36557d744841b08c8bf4e14 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:48 -0400 Subject: [PATCH 147/539] compat/hstrerror: convert sprintf to snprintf This is a trivially correct use of sprintf, as our error number should not be excessively long. But it's still nice to drop an sprintf call. Note that we cannot use xsnprintf here, because this is compat code which does not load git-compat-util.h. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- compat/hstrerror.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/hstrerror.c b/compat/hstrerror.c index 069c555da47ea1..b85a2fa9561f48 100644 --- a/compat/hstrerror.c +++ b/compat/hstrerror.c @@ -16,6 +16,6 @@ const char *githstrerror(int err) case TRY_AGAIN: return "Non-authoritative \"host not found\", or SERVERFAIL"; } - sprintf(buffer, "Name resolution error %d", err); + snprintf(buffer, sizeof(buffer), "Name resolution error %d", err); return buffer; } From 19bdd3e7e160a0b000c15d8bf6d33f4149e3f911 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:51 -0400 Subject: [PATCH 148/539] grep: use xsnprintf to format failure message This looks at first glance like the sprintf can overflow our buffer, but it's actually fine; the p->origin string is something constant and small, like "command line" or "-e option". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- grep.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grep.c b/grep.c index b58c7c64342698..6c68d5bd2e3353 100644 --- a/grep.c +++ b/grep.c @@ -306,9 +306,9 @@ static NORETURN void compile_regexp_failed(const struct grep_pat *p, char where[1024]; if (p->no) - sprintf(where, "In '%s' at %d, ", p->origin, p->no); + xsnprintf(where, sizeof(where), "In '%s' at %d, ", p->origin, p->no); else if (p->origin) - sprintf(where, "%s, ", p->origin); + xsnprintf(where, sizeof(where), "%s, ", p->origin); else where[0] = 0; From 330c8e26701c33f1d74dbe3c2692f29b5ed4ba5f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:53 -0400 Subject: [PATCH 149/539] entry.c: convert strcpy to xsnprintf This particular conversion is non-obvious, because nobody has passed our function the length of the destination buffer. However, the interface to checkout_entry specifies that the buffer must be at least TEMPORARY_FILENAME_LENGTH bytes long, so we can check that (meaning the existing code was not buggy, but merely worrisome to somebody reading it). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- entry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/entry.c b/entry.c index 1eda8e94714bbd..582c40071a3034 100644 --- a/entry.c +++ b/entry.c @@ -96,8 +96,8 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf { int symlink = (ce->ce_mode & S_IFMT) != S_IFREG; if (to_tempfile) { - strcpy(path, symlink - ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX"); + xsnprintf(path, TEMPORARY_FILENAME_LENGTH, "%s", + symlink ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX"); return mkstemp(path); } else { return create_file(path, !symlink ? ce->ce_mode : 0666); From 48bcc1c3cc09db1a6da0ce47460fae6e5f7edd4b Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:55 -0400 Subject: [PATCH 150/539] add_packed_git: convert strcpy into xsnprintf We have the path "foo.idx", and we create a buffer big enough to hold "foo.pack" and "foo.keep", and then strcpy straight into it. This isn't a bug (we have enough space), but it's very hard to tell from the strcpy that this is so. Let's instead use strip_suffix to take off the ".idx", record the size of our allocation, and use xsnprintf to make sure we don't violate our assumptions. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- cache.h | 2 +- sha1_file.c | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cache.h b/cache.h index 030b88025705bd..d206d645508a4b 100644 --- a/cache.h +++ b/cache.h @@ -1309,7 +1309,7 @@ extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); extern void free_pack_by_name(const char *); extern void clear_delta_base_cache(void); -extern struct packed_git *add_packed_git(const char *, int, int); +extern struct packed_git *add_packed_git(const char *path, size_t path_len, int local); /* * Return the SHA-1 of the nth object within the specified packfile. diff --git a/sha1_file.c b/sha1_file.c index f1060914745bb0..592226eb7bb9e9 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1146,11 +1146,12 @@ static void try_to_free_pack_memory(size_t size) release_pack_memory(size); } -struct packed_git *add_packed_git(const char *path, int path_len, int local) +struct packed_git *add_packed_git(const char *path, size_t path_len, int local) { static int have_set_try_to_free_routine; struct stat st; - struct packed_git *p = alloc_packed_git(path_len + 2); + size_t alloc; + struct packed_git *p; if (!have_set_try_to_free_routine) { have_set_try_to_free_routine = 1; @@ -1161,18 +1162,22 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) * Make sure a corresponding .pack file exists and that * the index looks sane. */ - path_len -= strlen(".idx"); - if (path_len < 1) { - free(p); + if (!strip_suffix_mem(path, &path_len, ".idx")) return NULL; - } + + /* + * ".pack" is long enough to hold any suffix we're adding (and + * the use xsnprintf double-checks that) + */ + alloc = path_len + strlen(".pack") + 1; + p = alloc_packed_git(alloc); memcpy(p->pack_name, path, path_len); - strcpy(p->pack_name + path_len, ".keep"); + xsnprintf(p->pack_name + path_len, alloc - path_len, ".keep"); if (!access(p->pack_name, F_OK)) p->pack_keep = 1; - strcpy(p->pack_name + path_len, ".pack"); + xsnprintf(p->pack_name + path_len, alloc - path_len, ".pack"); if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) { free(p); return NULL; From 0cc41428596ec1cd3862918ef781793ef7346ba5 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:06:58 -0400 Subject: [PATCH 151/539] http-push: replace strcat with xsnprintf We account for these strcats in our initial allocation, but the code is confusing to follow and verify. Let's remember our original allocation length, and then xsnprintf can verify that we don't exceed it. Note that we can't just use xstrfmt here (which would be even cleaner) because the code tries to grow the buffer only when necessary. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http-push.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/http-push.c b/http-push.c index 1f3788f634ba29..37baff818f03f6 100644 --- a/http-push.c +++ b/http-push.c @@ -786,21 +786,21 @@ xml_start_tag(void *userData, const char *name, const char **atts) { struct xml_ctx *ctx = (struct xml_ctx *)userData; const char *c = strchr(name, ':'); - int new_len; + int old_namelen, new_len; if (c == NULL) c = name; else c++; - new_len = strlen(ctx->name) + strlen(c) + 2; + old_namelen = strlen(ctx->name); + new_len = old_namelen + strlen(c) + 2; if (new_len > ctx->len) { ctx->name = xrealloc(ctx->name, new_len); ctx->len = new_len; } - strcat(ctx->name, "."); - strcat(ctx->name, c); + xsnprintf(ctx->name + old_namelen, ctx->len - old_namelen, ".%s", c); free(ctx->cdata); ctx->cdata = NULL; From b7115a350b5c01ce0ae7a8735e4235d4b2367b5f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:00 -0400 Subject: [PATCH 152/539] receive-pack: convert strncpy to xsnprintf This strncpy is pointless; we pass the strlen() of the src string, meaning that it works just like a memcpy. Worse, though, is that the size has no relation to the destination buffer, meaning it is a potential overflow. In practice, it's not. We pass only short constant strings like "warning: " and "error: ", which are much smaller than the destination buffer. We can make this much simpler by just using xsnprintf, which will check for overflow and return the size for our next vsnprintf, without us having to run a separate strlen(). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/receive-pack.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e6b93d026478dc..04d2bdf3f34212 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -280,10 +280,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2 static void report_message(const char *prefix, const char *err, va_list params) { - int sz = strlen(prefix); + int sz; char msg[4096]; - strncpy(msg, prefix, sz); + sz = xsnprintf(msg, sizeof(msg), "%s", prefix); sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params); if (sz > (sizeof(msg) - 1)) sz = sizeof(msg) - 1; From 75faa45ae0230b321bf72027b2274315d7e14e34 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:03 -0400 Subject: [PATCH 153/539] replace trivial malloc + sprintf / strcpy calls with xstrfmt It's a common pattern to do: foo = xmalloc(strlen(one) + strlen(two) + 1 + 1); sprintf(foo, "%s %s", one, two); (or possibly some variant with strcpy()s or a more complicated length computation). We can switch these to use xstrfmt, which is shorter, involves less error-prone manual computation, and removes many sprintf and strcpy calls which make it harder to audit the code for real buffer overflows. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/apply.c | 5 +---- builtin/ls-remote.c | 8 ++------ builtin/name-rev.c | 13 +++++-------- environment.c | 7 ++----- imap-send.c | 5 ++--- reflog-walk.c | 7 +++---- remote.c | 7 +------ setup.c | 12 +++--------- unpack-trees.c | 4 +--- 9 files changed, 20 insertions(+), 48 deletions(-) diff --git a/builtin/apply.c b/builtin/apply.c index 4aa53f7fd87d5f..094a20f4896fa0 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -698,10 +698,7 @@ static char *find_name_common(const char *line, const char *def, } if (root) { - char *ret = xmalloc(root_len + len + 1); - strcpy(ret, root); - memcpy(ret + root_len, start, len); - ret[root_len + len] = '\0'; + char *ret = xstrfmt("%s%.*s", root, len, start); return squash_slash(ret); } diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 4554dbc8a98c0d..5b6d679a639d23 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -93,12 +93,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (argv[i]) { int j; pattern = xcalloc(argc - i + 1, sizeof(const char *)); - for (j = i; j < argc; j++) { - int len = strlen(argv[j]); - char *p = xmalloc(len + 3); - sprintf(p, "*/%s", argv[j]); - pattern[j - i] = p; - } + for (j = i; j < argc; j++) + pattern[j - i] = xstrfmt("*/%s", argv[j]); } remote = remote_get(dest); if (!remote) { diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 248a3eb260cfa2..8a3a0cd61ef40e 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -56,19 +56,16 @@ static void name_rev(struct commit *commit, parents = parents->next, parent_number++) { if (parent_number > 1) { int len = strlen(tip_name); - char *new_name = xmalloc(len + - 1 + decimal_length(generation) + /* ~ */ - 1 + 2 + /* ^NN */ - 1); + char *new_name; if (len > 2 && !strcmp(tip_name + len - 2, "^0")) len -= 2; if (generation > 0) - sprintf(new_name, "%.*s~%d^%d", len, tip_name, - generation, parent_number); + new_name = xstrfmt("%.*s~%d^%d", len, tip_name, + generation, parent_number); else - sprintf(new_name, "%.*s^%d", len, tip_name, - parent_number); + new_name = xstrfmt("%.*s^%d", len, tip_name, + parent_number); name_rev(parents->item, new_name, 0, distance + MERGE_TRAVERSAL_WEIGHT, 0); diff --git a/environment.c b/environment.c index a533aed630c20a..c5b65f5e231e02 100644 --- a/environment.c +++ b/environment.c @@ -143,11 +143,8 @@ static char *git_path_from_env(const char *envvar, const char *git_dir, const char *path, int *fromenv) { const char *value = getenv(envvar); - if (!value) { - char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2); - sprintf(buf, "%s/%s", git_dir, path); - return buf; - } + if (!value) + return xstrfmt("%s/%s", git_dir, path); if (fromenv) *fromenv = 1; return xstrdup(value); diff --git a/imap-send.c b/imap-send.c index 37ac4aa86a740e..e9faaeaf2ab314 100644 --- a/imap-send.c +++ b/imap-send.c @@ -889,9 +889,8 @@ static char *cram(const char *challenge_64, const char *user, const char *pass) } /* response: " " */ - resp_len = strlen(user) + 1 + strlen(hex) + 1; - response = xmalloc(resp_len); - sprintf(response, "%s %s", user, hex); + response = xstrfmt("%s %s", user, hex); + resp_len = strlen(response) + 1; response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1); encoded_len = EVP_EncodeBlock((unsigned char *)response_64, diff --git a/reflog-walk.c b/reflog-walk.c index f8e743a23bef06..85b8a54241048b 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -56,12 +56,11 @@ static struct complete_reflogs *read_complete_reflog(const char *ref) } } if (reflogs->nr == 0) { - int len = strlen(ref); - char *refname = xmalloc(len + 12); - sprintf(refname, "refs/%s", ref); + char *refname = xstrfmt("refs/%s", ref); for_each_reflog_ent(refname, read_one_reflog, reflogs); if (reflogs->nr == 0) { - sprintf(refname, "refs/heads/%s", ref); + free(refname); + refname = xstrfmt("refs/heads/%s", ref); for_each_reflog_ent(refname, read_one_reflog, reflogs); } free(refname); diff --git a/remote.c b/remote.c index 26504b744786c6..5ab0f7f7a5443a 100644 --- a/remote.c +++ b/remote.c @@ -65,7 +65,6 @@ static int valid_remote(const struct remote *remote) static const char *alias_url(const char *url, struct rewrites *r) { int i, j; - char *ret; struct counted_string *longest; int longest_i; @@ -86,11 +85,7 @@ static const char *alias_url(const char *url, struct rewrites *r) if (!longest) return url; - ret = xmalloc(r->rewrite[longest_i]->baselen + - (strlen(url) - longest->len) + 1); - strcpy(ret, r->rewrite[longest_i]->base); - strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len); - return ret; + return xstrfmt("%s%s", r->rewrite[longest_i]->base, url + longest->len); } static void add_push_refspec(struct remote *remote, const char *ref) diff --git a/setup.c b/setup.c index a17c51e61d75ac..2b64cbbbfac60a 100644 --- a/setup.c +++ b/setup.c @@ -99,10 +99,7 @@ char *prefix_path_gently(const char *prefix, int len, return NULL; } } else { - sanitized = xmalloc(len + strlen(path) + 1); - if (len) - memcpy(sanitized, prefix, len); - strcpy(sanitized + len, path); + sanitized = xstrfmt("%.*s%s", len, prefix, path); if (remaining_prefix) *remaining_prefix = len; if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) { @@ -468,11 +465,8 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) { size_t pathlen = slash+1 - path; - size_t dirlen = pathlen + len - 8; - dir = xmalloc(dirlen + 1); - strncpy(dir, path, pathlen); - strncpy(dir + pathlen, buf + 8, len - 8); - dir[dirlen] = '\0'; + dir = xstrfmt("%.*s%.*s", (int)pathlen, path, + (int)(len - 8), buf + 8); free(buf); buf = dir; } diff --git a/unpack-trees.c b/unpack-trees.c index f932e80e862cfa..8e2032f4e59291 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1350,9 +1350,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce, * Then we need to make sure that we do not lose a locally * present file that is not ignored. */ - pathbuf = xmalloc(namelen + 2); - memcpy(pathbuf, ce->name, namelen); - strcpy(pathbuf+namelen, "/"); + pathbuf = xstrfmt("%.*s/", namelen, ce->name); memset(&d, 0, sizeof(d)); if (o->dir) From 3ec832c4b563f3dd1f23399c3bdede1168cc77e7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:05 -0400 Subject: [PATCH 154/539] config: use xstrfmt in normalize_value We xmalloc a fixed-size buffer and sprintf into it; this is OK because the size of our formatting types is finite, but that's not immediately clear to a reader auditing sprintf calls. Let's switch to xstrfmt, which is shorter and obviously correct. Note that just dropping the common xmalloc here causes gcc to complain with -Wmaybe-uninitialized. That's because if "types" does not match any of our known types, we never write anything into the "normalized" pointer. With the current code, gcc doesn't notice because we always return a valid pointer (just one which might point to uninitialized data, but the compiler doesn't know that). In other words, the current code is potentially buggy if new types are added without updating this spot. So let's take this opportunity to clean up the function a bit more. We can drop the "normalized" pointer entirely, and just return directly from each code path. And then add an assertion at the end in case we haven't covered any cases. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/config.c | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 71acc4414352e8..adc772786a7ddf 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -246,8 +246,6 @@ static int get_value(const char *key_, const char *regex_) static char *normalize_value(const char *key, const char *value) { - char *normalized; - if (!value) return NULL; @@ -258,27 +256,21 @@ static char *normalize_value(const char *key, const char *value) * "~/foobar/" in the config file, and to expand the ~ * when retrieving the value. */ - normalized = xstrdup(value); - else { - normalized = xmalloc(64); - if (types == TYPE_INT) { - int64_t v = git_config_int64(key, value); - sprintf(normalized, "%"PRId64, v); - } - else if (types == TYPE_BOOL) - sprintf(normalized, "%s", - git_config_bool(key, value) ? "true" : "false"); - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool) - sprintf(normalized, "%d", v); - else - sprintf(normalized, "%s", v ? "true" : "false"); - } + return xstrdup(value); + if (types == TYPE_INT) + return xstrfmt("%"PRId64, git_config_int64(key, value)); + if (types == TYPE_BOOL) + return xstrdup(git_config_bool(key, value) ? "true" : "false"); + if (types == TYPE_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool) + return xstrfmt("%d", v); + else + return xstrdup(v ? "true" : "false"); } - return normalized; + die("BUG: cannot normalize type %d", types); } static int get_color_found; From 2805bb59708e1cf049445fb8b21de1a710a3a16c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:07 -0400 Subject: [PATCH 155/539] fetch: replace static buffer with xstrfmt We parse the INFINITE_DEPTH constant into a static, fixed-size buffer using sprintf. This buffer is sufficiently large for the current constant, but it's a suspicious pattern, as the constant is defined far away, and it's not immediately obvious that 12 bytes are large enough to hold it. We can just use xstrfmt here, which gets rid of any question of the buffer size. It also removes any concerns with object lifetime, which means we do not have to wonder why this buffer deep within a conditional is marked "static" (we never free our newly allocated result, of course, but that's OK; it's global that lasts the lifetime of the whole program anyway). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/fetch.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index 9a3869f4ffd81f..470372552c4b18 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1156,11 +1156,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) die(_("--depth and --unshallow cannot be used together")); else if (!is_repository_shallow()) die(_("--unshallow on a complete repository does not make sense")); - else { - static char inf_depth[12]; - sprintf(inf_depth, "%d", INFINITE_DEPTH); - depth = inf_depth; - } + else + depth = xstrfmt("%d", INFINITE_DEPTH); } /* no need to be strict, transport_set_option() will validate it again */ From 9ae97018fb2e7f30ab92fdc2965d1dcff2c5c296 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:09 -0400 Subject: [PATCH 156/539] use strip_suffix and xstrfmt to replace suffix When we want to convert "foo.pack" to "foo.idx", we do it by duplicating the original string and then munging the bytes in place. Let's use strip_suffix and xstrfmt instead, which has several advantages: 1. It's more clear what the intent is. 2. It does not implicitly rely on the fact that strlen(".idx") <= strlen(".pack") to avoid an overflow. 3. We communicate the assumption that the input file ends with ".pack" (and get a run-time check that this is so). 4. We drop calls to strcpy, which makes auditing the code base easier. Likewise, we can do this to convert ".pack" to ".bitmap", avoiding some manual memory computation. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http.c | 7 ++++--- pack-bitmap.c | 13 ++++--------- sha1_file.c | 6 ++++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/http.c b/http.c index 7b0225996189fc..e0ff876cd96c2e 100644 --- a/http.c +++ b/http.c @@ -1511,6 +1511,7 @@ int finish_http_pack_request(struct http_pack_request *preq) struct packed_git **lst; struct packed_git *p = preq->target; char *tmp_idx; + size_t len; struct child_process ip = CHILD_PROCESS_INIT; const char *ip_argv[8]; @@ -1524,9 +1525,9 @@ int finish_http_pack_request(struct http_pack_request *preq) lst = &((*lst)->next); *lst = (*lst)->next; - tmp_idx = xstrdup(preq->tmpfile); - strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"), - ".idx.temp"); + if (!strip_suffix(preq->tmpfile, ".pack.temp", &len)) + die("BUG: pack tmpfile does not end in .pack.temp?"); + tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile); ip_argv[0] = "index-pack"; ip_argv[1] = "-o"; diff --git a/pack-bitmap.c b/pack-bitmap.c index 637770af813eee..7dfcb341d61aa9 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -252,16 +252,11 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) static char *pack_bitmap_filename(struct packed_git *p) { - char *idx_name; - int len; - - len = strlen(p->pack_name) - strlen(".pack"); - idx_name = xmalloc(len + strlen(".bitmap") + 1); - - memcpy(idx_name, p->pack_name, len); - memcpy(idx_name + len, ".bitmap", strlen(".bitmap") + 1); + size_t len; - return idx_name; + if (!strip_suffix(p->pack_name, ".pack", &len)) + die("BUG: pack_name does not end in .pack"); + return xstrfmt("%.*s.bitmap", (int)len, p->pack_name); } static int open_pack_bitmap_1(struct packed_git *packfile) diff --git a/sha1_file.c b/sha1_file.c index 592226eb7bb9e9..2be1afdde0b1bd 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -671,13 +671,15 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) int open_pack_index(struct packed_git *p) { char *idx_name; + size_t len; int ret; if (p->index_data) return 0; - idx_name = xstrdup(p->pack_name); - strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx"); + if (!strip_suffix(p->pack_name, ".pack", &len)) + die("BUG: pack_name does not end in .pack"); + idx_name = xstrfmt("%.*s.idx", (int)len, p->pack_name); ret = check_packed_git_idx(idx_name, p); free(idx_name); return ret; From a5e03bf5c686e9f2da5e94e091e0ea531d40c6b8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:12 -0400 Subject: [PATCH 157/539] ref-filter: drop sprintf and strcpy calls The ref-filter code comes from for-each-ref, and inherited a number of raw sprintf and strcpy calls. These are generally all safe, as we custom-size the buffers, or are formatting numbers into sufficiently large buffers. But we can make the resulting code even simpler and more obviously correct by using some of our helper functions. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ref-filter.c | 70 +++++++++++++++++----------------------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index f38dee4f605df3..1f718704e79c5f 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -192,9 +192,7 @@ static int grab_objectname(const char *name, const unsigned char *sha1, struct atom_value *v) { if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(sha1)); - v->s = s; + v->s = xstrdup(sha1_to_hex(sha1)); return 1; } if (!strcmp(name, "objectname:short")) { @@ -219,10 +217,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object if (!strcmp(name, "objecttype")) v->s = typename(obj->type); else if (!strcmp(name, "objectsize")) { - char *s = xmalloc(40); - sprintf(s, "%lu", sz); v->ul = sz; - v->s = s; + v->s = xstrfmt("%lu", sz); } else if (deref) grab_objectname(name, obj->sha1, v); @@ -246,11 +242,8 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob v->s = tag->tag; else if (!strcmp(name, "type") && tag->tagged) v->s = typename(tag->tagged->type); - else if (!strcmp(name, "object") && tag->tagged) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(tag->tagged->sha1)); - v->s = s; - } + else if (!strcmp(name, "object") && tag->tagged) + v->s = xstrdup(sha1_to_hex(tag->tagged->sha1)); } } @@ -268,32 +261,22 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object if (deref) name++; if (!strcmp(name, "tree")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(commit->tree->object.sha1)); - v->s = s; + v->s = xstrdup(sha1_to_hex(commit->tree->object.sha1)); } - if (!strcmp(name, "numparent")) { - char *s = xmalloc(40); + else if (!strcmp(name, "numparent")) { v->ul = commit_list_count(commit->parents); - sprintf(s, "%lu", v->ul); - v->s = s; + v->s = xstrfmt("%lu", v->ul); } else if (!strcmp(name, "parent")) { - int num = commit_list_count(commit->parents); - int i; struct commit_list *parents; - char *s = xmalloc(41 * num + 1); - v->s = s; - for (i = 0, parents = commit->parents; - parents; - parents = parents->next, i = i + 41) { + struct strbuf s = STRBUF_INIT; + for (parents = commit->parents; parents; parents = parents->next) { struct commit *parent = parents->item; - strcpy(s+i, sha1_to_hex(parent->object.sha1)); - if (parents->next) - s[i+40] = ' '; + if (parents != commit->parents) + strbuf_addch(&s, ' '); + strbuf_addstr(&s, sha1_to_hex(parent->object.sha1)); } - if (!i) - *s = '\0'; + v->s = strbuf_detach(&s, NULL); } } } @@ -700,7 +683,6 @@ static void populate_value(struct ref_array_item *ref) else if (!strcmp(formatp, "track") && (starts_with(name, "upstream") || starts_with(name, "push"))) { - char buf[40]; if (stat_tracking_info(branch, &num_ours, &num_theirs, NULL)) @@ -708,17 +690,13 @@ static void populate_value(struct ref_array_item *ref) if (!num_ours && !num_theirs) v->s = ""; - else if (!num_ours) { - sprintf(buf, "[behind %d]", num_theirs); - v->s = xstrdup(buf); - } else if (!num_theirs) { - sprintf(buf, "[ahead %d]", num_ours); - v->s = xstrdup(buf); - } else { - sprintf(buf, "[ahead %d, behind %d]", - num_ours, num_theirs); - v->s = xstrdup(buf); - } + else if (!num_ours) + v->s = xstrfmt("[behind %d]", num_theirs); + else if (!num_theirs) + v->s = xstrfmt("[ahead %d]", num_ours); + else + v->s = xstrfmt("[ahead %d, behind %d]", + num_ours, num_theirs); continue; } else if (!strcmp(formatp, "trackshort") && (starts_with(name, "upstream") || @@ -745,12 +723,8 @@ static void populate_value(struct ref_array_item *ref) if (!deref) v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } + else + v->s = xstrfmt("%s^{}", refname); } for (i = 0; i < used_atom_cnt; i++) { From acd47eec992160d76d5d4a2e98940de3fdc10552 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:14 -0400 Subject: [PATCH 158/539] help: drop prepend function in favor of xstrfmt This function predates xstrfmt, and its functionality is a subset. Let's just use xstrfmt. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/help.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/builtin/help.c b/builtin/help.c index 3422e7307998b9..fba8c01e242deb 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -295,16 +295,6 @@ static int is_git_command(const char *s) is_in_cmdlist(&other_cmds, s); } -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - static const char *cmd_to_page(const char *git_cmd) { if (!git_cmd) @@ -312,9 +302,9 @@ static const char *cmd_to_page(const char *git_cmd) else if (starts_with(git_cmd, "git")) return git_cmd; else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); + return xstrfmt("git-%s", git_cmd); else - return prepend("git", git_cmd); + return xstrfmt("git%s", git_cmd); } static void setup_man_path(void) From c978610dc841efe300bf4d1ee61b24d78b13c4f4 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:16 -0400 Subject: [PATCH 159/539] mailmap: replace strcpy with xstrdup We want to make a copy of a string without any leading whitespace. To do so, we allocate a buffer large enough to hold the original, skip past the whitespace, then copy that. It's much simpler to just allocate after we've skipped, in which case we can just copy the remainder of the string, leaving no question of whether "len" is large enough. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- mailmap.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mailmap.c b/mailmap.c index 9e9589730fa44c..f4a0f1cf27bf0a 100644 --- a/mailmap.c +++ b/mailmap.c @@ -162,11 +162,10 @@ static void read_mailmap_line(struct string_list *map, char *buffer, char *cp; free(*repo_abbrev); - *repo_abbrev = xmalloc(len); for (cp = buffer + abblen; isspace(*cp); cp++) ; /* nothing */ - strcpy(*repo_abbrev, cp); + *repo_abbrev = xstrdup(cp); } return; } From f28e3ab231d36c454445fe778ffb931920606109 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:18 -0400 Subject: [PATCH 160/539] read_branches_file: simplify string handling This function does a lot of manual string handling, and has some unnecessary limits. This patch cleans up a number of things: 1. Drop the arbitrary 1000-byte limit on the size of the remote name (we do not have such a limit in any of the other remote-reading mechanisms). 2. Replace fgets into a fixed-size buffer with a strbuf, eliminating any limits on the length of the URL. 3. Replace manual whitespace handling with strbuf_trim (since we now have a strbuf). This also gets rid of a call to strcpy, and the confusing reuse of the "p" pointer for multiple purposes. 4. We currently build up the refspecs over multiple strbuf calls. We do this to handle the fact that the URL "frag" may not be present. But rather than have multiple conditionals, let's just default "frag" to "master". This lets us format the refspecs with a single xstrfmt. It's shorter, and easier to see what the final string looks like. We also update the misleading comment in this area (the local branch is named after the remote name, not after the branch name on the remote side). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- remote.c | 54 ++++++++++++++++++++---------------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/remote.c b/remote.c index 5ab0f7f7a5443a..22a60fcc0bbd82 100644 --- a/remote.c +++ b/remote.c @@ -293,56 +293,42 @@ static void read_remotes_file(struct remote *remote) static void read_branches_file(struct remote *remote) { char *frag; - struct strbuf branch = STRBUF_INIT; - int n = 1000; - FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r"); - char *s, *p; - int len; + struct strbuf buf = STRBUF_INIT; + FILE *f = fopen(git_path("branches/%s", remote->name), "r"); if (!f) return; - s = fgets(buffer, BUF_SIZE, f); - fclose(f); - if (!s) - return; - while (isspace(*s)) - s++; - if (!*s) + + strbuf_getline(&buf, f, '\n'); + strbuf_trim(&buf); + if (!buf.len) { + strbuf_release(&buf); return; + } + remote->origin = REMOTE_BRANCHES; - p = s + strlen(s); - while (isspace(p[-1])) - *--p = 0; - len = p - s; - p = xmalloc(len + 1); - strcpy(p, s); /* * The branches file would have URL and optionally * #branch specified. The "master" (or specified) branch is - * fetched and stored in the local branch of the same name. + * fetched and stored in the local branch matching the + * remote name. */ - frag = strchr(p, '#'); - if (frag) { + frag = strchr(buf.buf, '#'); + if (frag) *(frag++) = '\0'; - strbuf_addf(&branch, "refs/heads/%s", frag); - } else - strbuf_addstr(&branch, "refs/heads/master"); + else + frag = "master"; + + add_url_alias(remote, strbuf_detach(&buf, NULL)); + add_fetch_refspec(remote, xstrfmt("refs/heads/%s:refs/heads/%s", + frag, remote->name)); - strbuf_addf(&branch, ":refs/heads/%s", remote->name); - add_url_alias(remote, p); - add_fetch_refspec(remote, strbuf_detach(&branch, NULL)); /* * Cogito compatible push: push current HEAD to remote #branch * (master if missing) */ - strbuf_init(&branch, 0); - strbuf_addstr(&branch, "HEAD"); - if (frag) - strbuf_addf(&branch, ":refs/heads/%s", frag); - else - strbuf_addstr(&branch, ":refs/heads/master"); - add_push_refspec(remote, strbuf_detach(&branch, NULL)); + add_push_refspec(remote, xstrfmt("HEAD:refs/heads/%s", frag)); remote->fetch_tags = 1; /* always auto-follow */ } From 0e265a92a1d2b9275d638f696c65c9bbe747e78c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:20 -0400 Subject: [PATCH 161/539] read_remotes_file: simplify string handling The main motivation for this cleanup is to switch our line-reading to a strbuf, which removes the use of a fixed-size buffer (which limited the size of remote URLs). Since we have the strbuf, we can make use of strbuf_rtrim(). While we're here, we can also simplify the parsing of each line. First, we can use skip_prefix() to avoid some magic numbers. But second, we can avoid splitting the parsing and actions for each line into two stages. Right now we figure out which type of line we have, set an int to a magic number, skip any intermediate whitespace, and then act on the resulting value based on the magic number. Instead, let's factor the whitespace skipping into a function. That lets us avoid the magic numbers and keep the actions close to the parsing. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- remote.c | 55 ++++++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/remote.c b/remote.c index 22a60fcc0bbd82..a01f13a211c749 100644 --- a/remote.c +++ b/remote.c @@ -54,9 +54,6 @@ static const char *pushremote_name; static struct rewrites rewrites; static struct rewrites rewrites_push; -#define BUF_SIZE (2048) -static char buffer[BUF_SIZE]; - static int valid_remote(const struct remote *remote) { return (!!remote->url) || (!!remote->foreign_vcs); @@ -243,50 +240,34 @@ static void add_instead_of(struct rewrite *rewrite, const char *instead_of) rewrite->instead_of_nr++; } +static const char *skip_spaces(const char *s) +{ + while (isspace(*s)) + s++; + return s; +} + static void read_remotes_file(struct remote *remote) { + struct strbuf buf = STRBUF_INIT; FILE *f = fopen(git_path("remotes/%s", remote->name), "r"); if (!f) return; remote->origin = REMOTE_REMOTES; - while (fgets(buffer, BUF_SIZE, f)) { - int value_list; - char *s, *p; - - if (starts_with(buffer, "URL:")) { - value_list = 0; - s = buffer + 4; - } else if (starts_with(buffer, "Push:")) { - value_list = 1; - s = buffer + 5; - } else if (starts_with(buffer, "Pull:")) { - value_list = 2; - s = buffer + 5; - } else - continue; - - while (isspace(*s)) - s++; - if (!*s) - continue; + while (strbuf_getline(&buf, f, '\n') != EOF) { + const char *v; - p = s + strlen(s); - while (isspace(p[-1])) - *--p = 0; + strbuf_rtrim(&buf); - switch (value_list) { - case 0: - add_url_alias(remote, xstrdup(s)); - break; - case 1: - add_push_refspec(remote, xstrdup(s)); - break; - case 2: - add_fetch_refspec(remote, xstrdup(s)); - break; - } + if (skip_prefix(buf.buf, "URL:", &v)) + add_url_alias(remote, xstrdup(skip_spaces(v))); + else if (skip_prefix(buf.buf, "Push:", &v)) + add_push_refspec(remote, xstrdup(skip_spaces(v))); + else if (skip_prefix(buf.buf, "Pull:", &v)) + add_fetch_refspec(remote, xstrdup(skip_spaces(v))); } + strbuf_release(&buf); fclose(f); } From 495127dbcbd53e89d7edee8db42bfa7e57c8a120 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:22 -0400 Subject: [PATCH 162/539] resolve_ref: use strbufs for internal buffers resolve_ref already uses a strbuf internally when generating pathnames, but it uses fixed-size buffers for storing the refname and symbolic refs. This means that you cannot actually point HEAD to a ref that is larger than 256 bytes. We can lift this limit by using strbufs here, too. Like sb_path, we pass the the buffers into our helper function, so that we can easily clean up all output paths. We can also drop the "unsafe" name from our helper function, as it no longer uses a single static buffer (but of course resolve_ref_unsafe is still unsafe, because the static buffers moved there). As a bonus, we also get to drop some strcpy calls between the two fixed buffers (that cannot currently overflow because the two buffers are sized identically). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- refs.c | 57 ++++++++++++++++++++++------------------- t/t1401-symbolic-ref.sh | 29 +++++++++++++++++++++ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/refs.c b/refs.c index d5c8b2f51b8ad6..c2709de2a0ac51 100644 --- a/refs.c +++ b/refs.c @@ -1579,16 +1579,15 @@ static int resolve_missing_loose_ref(const char *refname, } /* This function needs to return a meaningful errno on failure */ -static const char *resolve_ref_unsafe_1(const char *refname, - int resolve_flags, - unsigned char *sha1, - int *flags, - struct strbuf *sb_path) +static const char *resolve_ref_1(const char *refname, + int resolve_flags, + unsigned char *sha1, + int *flags, + struct strbuf *sb_refname, + struct strbuf *sb_path, + struct strbuf *sb_contents) { int depth = MAXDEPTH; - ssize_t len; - char buffer[256]; - static char refname_buffer[256]; int bad_name = 0; if (flags) @@ -1654,19 +1653,18 @@ static const char *resolve_ref_unsafe_1(const char *refname, /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { - len = readlink(path, buffer, sizeof(buffer)-1); - if (len < 0) { + strbuf_reset(sb_contents); + if (strbuf_readlink(sb_contents, path, 0) < 0) { if (errno == ENOENT || errno == EINVAL) /* inconsistent with lstat; retry */ goto stat_ref; else return NULL; } - buffer[len] = 0; - if (starts_with(buffer, "refs/") && - !check_refname_format(buffer, 0)) { - strcpy(refname_buffer, buffer); - refname = refname_buffer; + if (starts_with(sb_contents->buf, "refs/") && + !check_refname_format(sb_contents->buf, 0)) { + strbuf_swap(sb_refname, sb_contents); + refname = sb_refname->buf; if (flags) *flags |= REF_ISSYMREF; if (resolve_flags & RESOLVE_REF_NO_RECURSE) { @@ -1695,28 +1693,26 @@ static const char *resolve_ref_unsafe_1(const char *refname, else return NULL; } - len = read_in_full(fd, buffer, sizeof(buffer)-1); - if (len < 0) { + strbuf_reset(sb_contents); + if (strbuf_read(sb_contents, fd, 256) < 0) { int save_errno = errno; close(fd); errno = save_errno; return NULL; } close(fd); - while (len && isspace(buffer[len-1])) - len--; - buffer[len] = '\0'; + strbuf_rtrim(sb_contents); /* * Is it a symbolic ref? */ - if (!starts_with(buffer, "ref:")) { + if (!starts_with(sb_contents->buf, "ref:")) { /* * Please note that FETCH_HEAD has a second * line containing other data. */ - if (get_sha1_hex(buffer, sha1) || - (buffer[40] != '\0' && !isspace(buffer[40]))) { + if (get_sha1_hex(sb_contents->buf, sha1) || + (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) { if (flags) *flags |= REF_ISBROKEN; errno = EINVAL; @@ -1731,10 +1727,12 @@ static const char *resolve_ref_unsafe_1(const char *refname, } if (flags) *flags |= REF_ISSYMREF; - buf = buffer + 4; + buf = sb_contents->buf + 4; while (isspace(*buf)) buf++; - refname = strcpy(refname_buffer, buf); + strbuf_reset(sb_refname); + strbuf_addstr(sb_refname, buf); + refname = sb_refname->buf; if (resolve_flags & RESOLVE_REF_NO_RECURSE) { hashclr(sha1); return refname; @@ -1756,10 +1754,15 @@ static const char *resolve_ref_unsafe_1(const char *refname, const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { + static struct strbuf sb_refname = STRBUF_INIT; + struct strbuf sb_contents = STRBUF_INIT; struct strbuf sb_path = STRBUF_INIT; - const char *ret = resolve_ref_unsafe_1(refname, resolve_flags, - sha1, flags, &sb_path); + const char *ret; + + ret = resolve_ref_1(refname, resolve_flags, sha1, flags, + &sb_refname, &sb_path, &sb_contents); strbuf_release(&sb_path); + strbuf_release(&sb_contents); return ret; } diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index 36378b0e3f5b27..20b022ae339755 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -63,4 +63,33 @@ test_expect_success 'symbolic-ref fails to delete real ref' ' ' reset_to_sane +test_expect_success 'create large ref name' ' + # make 256+ character ref; some systems may not handle that, + # so be gentle + long=0123456789abcdef && + long=$long/$long/$long/$long && + long=$long/$long/$long/$long && + long_ref=refs/heads/$long && + tree=$(git write-tree) && + commit=$(echo foo | git commit-tree $tree) && + if git update-ref $long_ref $commit; then + test_set_prereq LONG_REF + else + echo >&2 "long refs not supported" + fi +' + +test_expect_success LONG_REF 'symbolic-ref can point to large ref name' ' + git symbolic-ref HEAD $long_ref && + echo $long_ref >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + +test_expect_success LONG_REF 'we can parse long symbolic ref' ' + echo $commit >expect && + git rev-parse --verify HEAD >actual && + test_cmp expect actual +' + test_done From 0cb9d6d6b63ad7fea4ca8363f7f1f921b1e16ec7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:25 -0400 Subject: [PATCH 163/539] upload-archive: convert sprintf to strbuf When we report an error to the client, we format it into a fixed-size buffer using vsprintf(). This can't actually overflow in practice, since we only format a very tame subset of strings (mostly strerror() output). However, it's hard to tell immediately, so let's just use a strbuf so readers do not have to wonder. We do add an allocation here, but the performance is not important; the next step is to call die() anyway. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/upload-archive.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 32ab94cd06b43c..dbfe14f3fec3f8 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -49,15 +49,14 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) __attribute__((format (printf, 1, 2))) static void error_clnt(const char *fmt, ...) { - char buf[1024]; + struct strbuf buf = STRBUF_INIT; va_list params; - int len; va_start(params, fmt); - len = vsprintf(buf, fmt, params); + strbuf_vaddf(&buf, fmt, params); va_end(params); - send_sideband(1, 3, buf, len, LARGE_PACKET_MAX); - die("sent error to the client: %s", buf); + send_sideband(1, 3, buf.buf, buf.len, LARGE_PACKET_MAX); + die("sent error to the client: %s", buf.buf); } static ssize_t process_input(int child_fd, int band) From df1ed03a6fabf58b63b26950c71d36eff4aa25e8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:27 -0400 Subject: [PATCH 164/539] remote-ext: simplify git pkt-line generation We format a pkt-line into a heap buffer, which requires manual computation of the required size, and uses some bare sprintf calls. We could use a strbuf instead, which would take care of the computation for us. But it's even easier still to use packet_write(). Besides handling the formatting and writing for us, it fixes two things: 1. Our manual max-size check used 0xFFFF, while technically LARGE_PACKET_MAX is slightly smaller than this. 2. Our packet will now be output as part of GIT_TRACE_PACKET debugging. Unfortunately packet_write() does not let us build up the buffer progressively, so we do have to repeat ourselves a little depending on the "vhost" setting, but the end result is still far more readable than the original. Since there were no tests covering this feature at all, we'll add a few into t5802. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/remote-ext.c | 34 +++++----------------------------- t/t5802-connect-helper.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 3b8c22cc75ed04..e3cd25d580b0f1 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "transport.h" #include "run-command.h" +#include "pkt-line.h" /* * URL syntax: @@ -142,36 +143,11 @@ static const char **parse_argv(const char *arg, const char *service) static void send_git_request(int stdin_fd, const char *serv, const char *repo, const char *vhost) { - size_t bufferspace; - size_t wpos = 0; - char *buffer; - - /* - * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and - * 6 bytes extra (xxxx \0) if there is no vhost. - */ - if (vhost) - bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; + if (!vhost) + packet_write(stdin_fd, "%s %s%c", serv, repo, 0); else - bufferspace = strlen(serv) + strlen(repo) + 6; - - if (bufferspace > 0xFFFF) - die("Request too large to send"); - buffer = xmalloc(bufferspace); - - /* Make the packet. */ - wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, - serv, repo, 0); - - /* Add vhost if any. */ - if (vhost) - sprintf(buffer + wpos, "host=%s%c", vhost, 0); - - /* Send the request */ - if (write_in_full(stdin_fd, buffer, bufferspace) < 0) - die_errno("Failed to send request"); - - free(buffer); + packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, + vhost, 0); } static int run_child(const char *arg, const char *service) diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh index 878faf2b631841..b7a7f9d5886f1e 100755 --- a/t/t5802-connect-helper.sh +++ b/t/t5802-connect-helper.sh @@ -69,4 +69,32 @@ test_expect_success 'update backfilled tag without primary transfer' ' test_cmp expect actual ' + +test_expect_success 'set up fake git-daemon' ' + mkdir remote && + git init --bare remote/one.git && + mkdir remote/host && + git init --bare remote/host/two.git && + write_script fake-daemon <<-\EOF && + git daemon --inetd \ + --informative-errors \ + --export-all \ + --base-path="$TRASH_DIRECTORY/remote" \ + --interpolated-path="$TRASH_DIRECTORY/remote/%H%D" \ + "$TRASH_DIRECTORY/remote" + EOF + export TRASH_DIRECTORY && + PATH=$TRASH_DIRECTORY:$PATH +' + +test_expect_success 'ext command can connect to git daemon (no vhost)' ' + rm -rf dst && + git clone "ext::fake-daemon %G/one.git" dst +' + +test_expect_success 'ext command can connect to git daemon (vhost)' ' + rm -rf dst && + git clone "ext::fake-daemon %G/two.git %Vhost" dst +' + test_done From 7d0581a9abe733d8880113370c4d956b50f5bd9f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:29 -0400 Subject: [PATCH 165/539] http-push: use strbuf instead of fwrite_buffer The http-push code defines an fwrite_buffer function for use as a curl callback; it just writes to a strbuf. There's no reason we need to use it ourselves, as we know we have a strbuf. This lets us format directly into it, rather than dealing with an extra temporary buffer (which required manual length computation). While we're here, let's also remove the literal tabs from the source in favor of "\t", which is more visually obvious. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http-push.c | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/http-push.c b/http-push.c index 37baff818f03f6..e501c282e67f35 100644 --- a/http-push.c +++ b/http-push.c @@ -1459,8 +1459,6 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls) { struct strbuf *buf = (struct strbuf *)ls->userData; struct object *o; - int len; - char *ref_info; struct ref *ref; ref = alloc_ref(ls->dentry_name); @@ -1484,23 +1482,14 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls) return; } - len = strlen(ls->dentry_name) + 42; - ref_info = xcalloc(len + 1, 1); - sprintf(ref_info, "%s %s\n", - sha1_to_hex(ref->old_sha1), ls->dentry_name); - fwrite_buffer(ref_info, 1, len, buf); - free(ref_info); + strbuf_addf(buf, "%s\t%s\n", + sha1_to_hex(ref->old_sha1), ls->dentry_name); if (o->type == OBJ_TAG) { o = deref_tag(o, ls->dentry_name, 0); - if (o) { - len = strlen(ls->dentry_name) + 45; - ref_info = xcalloc(len + 1, 1); - sprintf(ref_info, "%s %s^{}\n", - sha1_to_hex(o->sha1), ls->dentry_name); - fwrite_buffer(ref_info, 1, len, buf); - free(ref_info); - } + if (o) + strbuf_addf(buf, "%s\t%s^{}\n", + sha1_to_hex(o->sha1), ls->dentry_name); } free(ref); } From 54ba4c5fa2d7de216ca090ac2e657728462c81d5 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:31 -0400 Subject: [PATCH 166/539] http-walker: store url in a strbuf We do an unchecked sprintf directly into our url buffer. This doesn't overflow because we know that it was sized for "$base/objects/info/http-alternates", and we are writing "$base/objects/info/alternates", which must be smaller. But that is not immediately obvious to a reader who is looking for buffer overflows. Let's switch to a strbuf, so that we do not have to think about this issue at all. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http-walker.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/http-walker.c b/http-walker.c index 88da5468e77f5a..2c721f0c30d786 100644 --- a/http-walker.c +++ b/http-walker.c @@ -29,7 +29,7 @@ struct object_request { struct alternates_request { struct walker *walker; const char *base; - char *url; + struct strbuf *url; struct strbuf *buffer; struct active_request_slot *slot; int http_specific; @@ -195,10 +195,11 @@ static void process_alternates_response(void *callback_data) /* Try reusing the slot to get non-http alternates */ alt_req->http_specific = 0; - sprintf(alt_req->url, "%s/objects/info/alternates", - base); + strbuf_reset(alt_req->url); + strbuf_addf(alt_req->url, "%s/objects/info/alternates", + base); curl_easy_setopt(slot->curl, CURLOPT_URL, - alt_req->url); + alt_req->url->buf); active_requests++; slot->in_use = 1; if (slot->finished != NULL) @@ -312,7 +313,7 @@ static void process_alternates_response(void *callback_data) static void fetch_alternates(struct walker *walker, const char *base) { struct strbuf buffer = STRBUF_INIT; - char *url; + struct strbuf url = STRBUF_INIT; struct active_request_slot *slot; struct alternates_request alt_req; struct walker_data *cdata = walker->data; @@ -338,7 +339,7 @@ static void fetch_alternates(struct walker *walker, const char *base) if (walker->get_verbosely) fprintf(stderr, "Getting alternates list for %s\n", base); - url = xstrfmt("%s/objects/info/http-alternates", base); + strbuf_addf(&url, "%s/objects/info/http-alternates", base); /* * Use a callback to process the result, since another request @@ -351,10 +352,10 @@ static void fetch_alternates(struct walker *walker, const char *base) curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf); alt_req.base = base; - alt_req.url = url; + alt_req.url = &url; alt_req.buffer = &buffer; alt_req.http_specific = 1; alt_req.slot = slot; @@ -365,7 +366,7 @@ static void fetch_alternates(struct walker *walker, const char *base) cdata->got_alternates = -1; strbuf_release(&buffer); - free(url); + strbuf_release(&url); } static int fetch_indices(struct walker *walker, struct alt_base *repo) From ac5190cc48bd75586566ccc052304d40bbc63147 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 17:07:34 -0400 Subject: [PATCH 167/539] sha1_get_pack_name: use a strbuf We do some manual memory computation here, and there's no check that our 60 is not overflowed by the raw sprintf (it isn't, because the "which" parameter is never longer than "pack"). We can simplify this greatly with a strbuf. Technically the end result is not identical, as the original took care not to rewrite the object directory on each call for performance reasons. We could do that here, too (by saving the baselen and resetting to it), but it's not worth the complexity; this function is not called a lot (generally once per packfile that we open). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- sha1_file.c | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 2be1afdde0b1bd..c26fdcbd8840ed 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -208,44 +208,25 @@ const char *sha1_file_name(const unsigned char *sha1) * provided by the caller. which should be "pack" or "idx". */ static char *sha1_get_pack_name(const unsigned char *sha1, - char **name, char **base, const char *which) + struct strbuf *buf, + const char *which) { - static const char hex[] = "0123456789abcdef"; - char *buf; - int i; - - if (!*base) { - const char *sha1_file_directory = get_object_directory(); - int len = strlen(sha1_file_directory); - *base = xmalloc(len + 60); - sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.%s", - sha1_file_directory, which); - *name = *base + len + 11; - } - - buf = *name; - - for (i = 0; i < 20; i++) { - unsigned int val = *sha1++; - *buf++ = hex[val >> 4]; - *buf++ = hex[val & 0xf]; - } - - return *base; + strbuf_reset(buf); + strbuf_addf(buf, "%s/pack/pack-%s.%s", get_object_directory(), + sha1_to_hex(sha1), which); + return buf->buf; } char *sha1_pack_name(const unsigned char *sha1) { - static char *name, *base; - - return sha1_get_pack_name(sha1, &name, &base, "pack"); + static struct strbuf buf = STRBUF_INIT; + return sha1_get_pack_name(sha1, &buf, "pack"); } char *sha1_pack_index_name(const unsigned char *sha1) { - static char *name, *base; - - return sha1_get_pack_name(sha1, &name, &base, "idx"); + static struct strbuf buf = STRBUF_INIT; + return sha1_get_pack_name(sha1, &buf, "idx"); } struct alternate_object_database *alt_odb_list; From 31cd12837271fbf749835672a2954dcb98b0c070 Mon Sep 17 00:00:00 2001 From: Stephan Beyer Date: Fri, 25 Sep 2015 02:31:37 +0200 Subject: [PATCH 168/539] t/perf: make runner work even if Git is not installed aggregate.perl did not work when Git.pm is not installed to a directory contained in the default Perl library path list or PERLLIB. This commit prepends the Perl library path of the current Git source tree to enable this. Note that this commit adds a hard-coded relative path use lib '../../perl/blib/lib'; instead of the flexible environment-based variant use lib (split(/:/, $ENV{GITPERLLIB})); which is used in tests written in Perl. The hard-coded variant is used because the whole performance test framework does it that way (and GITPERLLIB is not set there). Signed-off-by: Stephan Beyer Signed-off-by: Junio C Hamano --- t/perf/aggregate.perl | 1 + 1 file changed, 1 insertion(+) diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 15f7fc1b801511..924b19dab4122a 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -1,5 +1,6 @@ #!/usr/bin/perl +use lib '../../perl/blib/lib'; use strict; use warnings; use Git; From be510e0105eaed87dca76fd71f13e22952640599 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Fri, 25 Sep 2015 13:59:35 +0100 Subject: [PATCH 169/539] Documentation: fix section header mark-up Asciidoctor is stricter than AsciiDoc when deciding if underlining is a section title or the start of preformatted text. Make the length of the underlining match the text to ensure that it renders correctly in all implementations. Signed-off-by: John Keeping [jc: squashed in git-bisect one noticed by Michael J Gruber] Signed-off-by: Junio C Hamano --- Documentation/git-bisect-lk2009.txt | 2 +- Documentation/user-manual.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt index afeb86c6cd4ed6..78b10659c5c488 100644 --- a/Documentation/git-bisect-lk2009.txt +++ b/Documentation/git-bisect-lk2009.txt @@ -1321,7 +1321,7 @@ So git bisect is unconditional goodness - and feel free to quote that _____________ Acknowledgments ----------------- +--------------- Many thanks to Junio Hamano for his help in reviewing this paper, for reviewing the patches I sent to the Git mailing list, for discussing diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 46aa6bc1a67bee..c964a8b0d67cfb 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -3424,7 +3424,7 @@ just missing one particular blob version. [[the-index]] The index ------------ +--------- The index is a binary file (generally kept in `.git/index`) containing a sorted list of path names, each with permissions and the SHA-1 of a blob From 5088d3b38775f8ac12d7f77636775b16059b67ef Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 22 Sep 2015 18:03:49 -0400 Subject: [PATCH 170/539] transport: refactor protocol whitelist code The current callers only want to die when their transport is prohibited. But future callers want to query the mechanism without dying. Let's break out a few query functions, and also save the results in a static list so we don't have to re-parse for each query. Based-on-a-patch-by: Blake Burkhart Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- transport.c | 38 ++++++++++++++++++++++++++++++-------- transport.h | 15 +++++++++++++-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/transport.c b/transport.c index 94fe8658f2bfa7..647d2c2afaadb4 100644 --- a/transport.c +++ b/transport.c @@ -909,18 +909,40 @@ static int external_specification_len(const char *url) return strchr(url, ':') - url; } -void transport_check_allowed(const char *type) +static const struct string_list *protocol_whitelist(void) { - struct string_list allowed = STRING_LIST_INIT_DUP; - const char *v = getenv("GIT_ALLOW_PROTOCOL"); + static int enabled = -1; + static struct string_list allowed = STRING_LIST_INIT_DUP; + + if (enabled < 0) { + const char *v = getenv("GIT_ALLOW_PROTOCOL"); + if (v) { + string_list_split(&allowed, v, ':', -1); + string_list_sort(&allowed); + enabled = 1; + } else { + enabled = 0; + } + } - if (!v) - return; + return enabled ? &allowed : NULL; +} + +int is_transport_allowed(const char *type) +{ + const struct string_list *allowed = protocol_whitelist(); + return !allowed || string_list_has_string(allowed, type); +} - string_list_split(&allowed, v, ':', -1); - if (!unsorted_string_list_has_string(&allowed, type)) +void transport_check_allowed(const char *type) +{ + if (!is_transport_allowed(type)) die("transport '%s' not allowed", type); - string_list_clear(&allowed, 0); +} + +int transport_restrict_protocols(void) +{ + return !!protocol_whitelist(); } struct transport *transport_get(struct remote *remote, const char *url) diff --git a/transport.h b/transport.h index f7df6ec1d2a1f1..ed84da2aa4f39d 100644 --- a/transport.h +++ b/transport.h @@ -132,13 +132,24 @@ struct transport { /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); +/* + * Check whether a transport is allowed by the environment. Type should + * generally be the URL scheme, as described in Documentation/git.txt + */ +int is_transport_allowed(const char *type); + /* * Check whether a transport is allowed by the environment, - * and die otherwise. type should generally be the URL scheme, - * as described in Documentation/git.txt + * and die otherwise. */ void transport_check_allowed(const char *type); +/* + * Returns true if the user has attempted to turn on protocol + * restrictions at all. + */ +int transport_restrict_protocols(void); + /* Transport options which apply to git:// and scp-style URLs */ /* The program to use on the remote side to send a pack */ From f4113cac0c88b4f36ee6f3abf3218034440a68e3 Mon Sep 17 00:00:00 2001 From: Blake Burkhart Date: Tue, 22 Sep 2015 18:06:04 -0400 Subject: [PATCH 171/539] http: limit redirection to protocol-whitelist Previously, libcurl would follow redirection to any protocol it was compiled for support with. This is desirable to allow redirection from HTTP to HTTPS. However, it would even successfully allow redirection from HTTP to SFTP, a protocol that git does not otherwise support at all. Furthermore git's new protocol-whitelisting could be bypassed by following a redirect within the remote helper, as it was only enforced at transport selection time. This patch limits redirects within libcurl to HTTP, HTTPS, FTP and FTPS. If there is a protocol-whitelist present, this list is limited to those also allowed by the whitelist. As redirection happens from within libcurl, it is impossible for an HTTP redirect to a protocol implemented within another remote helper. When the curl version git was compiled with is too old to support restrictions on protocol redirection, we warn the user if GIT_ALLOW_PROTOCOL restrictions were requested. This is a little inaccurate, as even without that variable in the environment, we would still restrict SFTP, etc, and we do not warn in that case. But anything else means we would literally warn every time git accesses an http remote. This commit includes a test, but it is not as robust as we would hope. It redirects an http request to ftp, and checks that curl complained about the protocol, which means that we are relying on curl's specific error message to know what happened. Ideally we would redirect to a working ftp server and confirm that we can clone without protocol restrictions, and not with them. But we do not have a portable way of providing an ftp server, nor any other protocol that curl supports (https is the closest, but we would have to deal with certificates). [jk: added test and version warning] Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git.txt | 5 ----- http.c | 17 +++++++++++++++++ t/lib-httpd/apache.conf | 1 + t/t5812-proto-disable-http.sh | 9 +++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index b6a12b32ee5b7b..41a09cac7af072 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -1071,11 +1071,6 @@ GIT_ICASE_PATHSPECS:: - any external helpers are named by their protocol (e.g., use `hg` to allow the `git-remote-hg` helper) -+ -Note that this controls only git's internal protocol selection. -If libcurl is used (e.g., by the `http` transport), it may -redirect to other protocols. There is not currently any way to -restrict this. Discussion[[Discussion]] diff --git a/http.c b/http.c index 67986200655f88..5a57bccea9e762 100644 --- a/http.c +++ b/http.c @@ -8,6 +8,7 @@ #include "credential.h" #include "version.h" #include "pkt-line.h" +#include "transport.h" int active_requests; int http_is_verbose; @@ -303,6 +304,7 @@ static void set_curl_keepalive(CURL *c) static CURL *get_curl_handle(void) { CURL *result = curl_easy_init(); + long allowed_protocols = 0; if (!result) die("curl_easy_init failed"); @@ -355,6 +357,21 @@ static CURL *get_curl_handle(void) #elif LIBCURL_VERSION_NUM >= 0x071101 curl_easy_setopt(result, CURLOPT_POST301, 1); #endif +#if LIBCURL_VERSION_NUM >= 0x071304 + if (is_transport_allowed("http")) + allowed_protocols |= CURLPROTO_HTTP; + if (is_transport_allowed("https")) + allowed_protocols |= CURLPROTO_HTTPS; + if (is_transport_allowed("ftp")) + allowed_protocols |= CURLPROTO_FTP; + if (is_transport_allowed("ftps")) + allowed_protocols |= CURLPROTO_FTPS; + curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols); +#else + if (transport_restrict_protocols()) + warning("protocol restrictions not applied to curl redirects because\n" + "your curl version is too old (>= 7.19.4)"); +#endif if (getenv("GIT_CURL_VERBOSE")) curl_easy_setopt(result, CURLOPT_VERBOSE, 1); diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 0b81a0047b8d9c..68ef8adb8e2b6c 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -119,6 +119,7 @@ RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301] RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302] RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301] RewriteRule ^/smart-redir-limited/(.*)/info/refs$ /smart/$1/info/refs [R=301] +RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302] LoadModule ssl_module modules/mod_ssl.so diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index dd5001cbac8a0a..6a4f81662d816e 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -16,5 +16,14 @@ test_expect_success 'create git-accessible repo' ' test_proto "smart http" http "$HTTPD_URL/smart/repo.git" +test_expect_success 'curl redirects respect whitelist' ' + test_must_fail env GIT_ALLOW_PROTOCOL=http:https \ + git clone "$HTTPD_URL/ftp-redir/repo.git" 2>stderr && + { + test_i18ngrep "ftp.*disabled" stderr || + test_i18ngrep "your curl version is too old" + } +' + stop_httpd test_done From b258116462399b318c86165c61a5c7123043cfd4 Mon Sep 17 00:00:00 2001 From: Blake Burkhart Date: Tue, 22 Sep 2015 18:06:20 -0400 Subject: [PATCH 172/539] http: limit redirection depth By default, libcurl will follow circular http redirects forever. Let's put a cap on this so that somebody who can trigger an automated fetch of an arbitrary repository (e.g., for CI) cannot convince git to loop infinitely. The value chosen is 20, which is the same default that Firefox uses. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http.c | 1 + t/lib-httpd/apache.conf | 3 +++ t/t5812-proto-disable-http.sh | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/http.c b/http.c index 5a57bccea9e762..00e3fc80e81610 100644 --- a/http.c +++ b/http.c @@ -352,6 +352,7 @@ static CURL *get_curl_handle(void) } curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20); #if LIBCURL_VERSION_NUM >= 0x071301 curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); #elif LIBCURL_VERSION_NUM >= 0x071101 diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 68ef8adb8e2b6c..7d15e6d44c83f6 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -121,6 +121,9 @@ RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301] RewriteRule ^/smart-redir-limited/(.*)/info/refs$ /smart/$1/info/refs [R=301] RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302] +RewriteRule ^/loop-redir/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-(.*) /$1 [R=302] +RewriteRule ^/loop-redir/(.*)$ /loop-redir/x-$1 [R=302] + LoadModule ssl_module modules/mod_ssl.so diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index 6a4f81662d816e..0d105d54174e06 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -25,5 +25,9 @@ test_expect_success 'curl redirects respect whitelist' ' } ' +test_expect_success 'curl limits redirects' ' + test_must_fail git clone "$HTTPD_URL/loop-redir/smart/repo.git" +' + stop_httpd test_done From 31041209fee1ebc6420b069e9c1e283241496545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 28 Sep 2015 20:06:12 +0700 Subject: [PATCH 173/539] t0002: add test for enter_repo(), non-strict mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t0002-gitfile.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 9393322c3e7028..545bfe29828fb2 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -116,4 +116,22 @@ test_expect_success 'setup_git_dir twice in subdir' ' ) ' +test_expect_success 'enter_repo non-strict mode' ' + test_create_repo enter_repo && + ( + cd enter_repo && + test_tick && + test_commit foo && + mv .git .realgit && + echo "gitdir: .realgit" >.git + ) && + git ls-remote enter_repo >actual && + cat >expected <<-\EOF && + 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + EOF + test_cmp expected actual +' + test_done From 0f64cc407f32f979c8bcfa7d3d9b24d8e023df35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 28 Sep 2015 20:06:13 +0700 Subject: [PATCH 174/539] enter_repo: avoid duplicating logic, use is_git_directory() instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It matters for linked checkouts where 'refs' directory won't be available in $GIT_DIR. is_git_directory() knows about $GIT_COMMON_DIR and can handle this case. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- path.c | 3 +-- t/t0002-gitfile.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/path.c b/path.c index a536ee329fd63c..7340e11d7db833 100644 --- a/path.c +++ b/path.c @@ -441,8 +441,7 @@ const char *enter_repo(const char *path, int strict) else if (chdir(path)) return NULL; - if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && - validate_headref("HEAD") == 0) { + if (is_git_directory(".")) { set_git_dir("."); check_repository_format(); return path; diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 545bfe29828fb2..2e709cc969b87c 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -134,4 +134,18 @@ test_expect_success 'enter_repo non-strict mode' ' test_cmp expected actual ' +test_expect_success 'enter_repo linked checkout' ' + ( + cd enter_repo && + git worktree add ../foo refs/tags/foo + ) && + git ls-remote foo >actual && + cat >expected <<-\EOF && + 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + EOF + test_cmp expected actual +' + test_done From 1f5fbe1fe2d04f6386cf8febc1ce308bcf815592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 28 Sep 2015 20:06:14 +0700 Subject: [PATCH 175/539] enter_repo: allow .git files in strict mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strict mode is about not guessing where .git is. If the user points to a .git file, we know exactly where the target .git dir will be. This makes it possible to serve .git files as repository on the server side. This may be needed even in local clone case because transport.c code uses upload-pack for fetching remote refs. But right now the clone/transport code goes with non-strict. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- path.c | 9 +++++++-- t/t0002-gitfile.sh | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/path.c b/path.c index 7340e11d7db833..a3461344623075 100644 --- a/path.c +++ b/path.c @@ -438,8 +438,13 @@ const char *enter_repo(const char *path, int strict) return NULL; path = validated_path; } - else if (chdir(path)) - return NULL; + else { + const char *gitfile = read_gitfile(path); + if (gitfile) + path = gitfile; + if (chdir(path)) + return NULL; + } if (is_git_directory(".")) { set_git_dir("."); diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 2e709cc969b87c..9670e8cbe6cb9a 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -148,4 +148,14 @@ test_expect_success 'enter_repo linked checkout' ' test_cmp expected actual ' +test_expect_success 'enter_repo strict mode' ' + git ls-remote --upload-pack="git upload-pack --strict" foo/.git >actual && + cat >expected <<-\EOF && + 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + EOF + test_cmp expected actual +' + test_done From 744e4697555d4982acf0862f8fa6b15dd4796c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 28 Sep 2015 20:06:15 +0700 Subject: [PATCH 176/539] clone: allow --local from a linked checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Noticed-by: Bjørnar Snoksrud Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/clone.c | 6 ++++-- t/t2025-worktree-add.sh | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 578da85254418a..39d4adf7d10e46 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -424,8 +424,10 @@ static void clone_local(const char *src_repo, const char *dest_repo) } else { struct strbuf src = STRBUF_INIT; struct strbuf dest = STRBUF_INIT; - strbuf_addf(&src, "%s/objects", src_repo); - strbuf_addf(&dest, "%s/objects", dest_repo); + get_common_dir(&src, src_repo); + get_common_dir(&dest, dest_repo); + strbuf_addstr(&src, "/objects"); + strbuf_addstr(&dest, "/objects"); copy_or_link_directory(&src, &dest, src_repo, src.len); strbuf_release(&src); strbuf_release(&dest); diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index 8267411a0ec129..369417498946fc 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -193,4 +193,9 @@ test_expect_success '"add" -B/--detach mutually exclusive' ' test_must_fail git worktree add -B poodle --detach bamboo master ' +test_expect_success 'local clone from linked checkout' ' + git clone --local here here-clone && + ( cd here-clone && git fsck ) +' + test_done From d78db8424ec5c3f3327441cd1a897064af39da91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 28 Sep 2015 20:06:16 +0700 Subject: [PATCH 177/539] clone: better error when --reference is a linked checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/clone.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin/clone.c b/builtin/clone.c index 39d4adf7d10e46..3e14491a340cdc 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -294,9 +294,14 @@ static int add_one_reference(struct string_list_item *item, void *cb_data) char *ref_git_git = mkpathdup("%s/.git", ref_git); free(ref_git); ref_git = ref_git_git; - } else if (!is_directory(mkpath("%s/objects", ref_git))) + } else if (!is_directory(mkpath("%s/objects", ref_git))) { + struct strbuf sb = STRBUF_INIT; + if (get_common_dir(&sb, ref_git)) + die(_("reference repository '%s' as a linked checkout is not supported yet."), + item->string); die(_("reference repository '%s' is not a local repository."), item->string); + } if (!access(mkpath("%s/shallow", ref_git), F_OK)) die(_("reference repository '%s' is shallow"), item->string); From 63ec5e1fecc14b0dd5452f0a2b80641600b03437 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 28 Sep 2015 18:12:18 +0200 Subject: [PATCH 178/539] setup: fix "inside work tree" detection on case-insensitive filesystems Git has a config variable to indicate that it is operating on a file system that is case-insensitive: core.ignoreCase. But the `dir_inside_of()` function did not respect that. As a result, if Git's idea of the current working directory disagreed in its upper/lower case with the `GIT_WORK_TREE` variable (e.g. `C:\test` vs `c:\test`) the user would be greeted by the error message fatal: git-am cannot be used without a working tree. when trying to run a rebase. This fixes https://github.com/git-for-windows/git/issues/402 (reported by Daniel Harding). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- dir.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dir.c b/dir.c index a71331af18c7da..22402c730efdf7 100644 --- a/dir.c +++ b/dir.c @@ -2030,6 +2030,15 @@ int file_exists(const char *f) return lstat(f, &sb) == 0; } +static int cmp_icase(char a, char b) +{ + if (a == b) + return 0; + if (ignore_case) + return toupper(a) - toupper(b); + return a - b; +} + /* * Given two normalized paths (a trailing slash is ok), if subdir is * outside dir, return -1. Otherwise return the offset in subdir that @@ -2041,7 +2050,7 @@ int dir_inside_of(const char *subdir, const char *dir) assert(dir && subdir && *dir && *subdir); - while (*dir && *subdir && *dir == *subdir) { + while (*dir && *subdir && !cmp_icase(*dir, *subdir)) { dir++; subdir++; offset++; From 29bc480aa1a33ace2866743fed2bf8aba5de4d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 28 Sep 2015 19:30:17 +0700 Subject: [PATCH 179/539] ls-remote.txt: delete unsupported option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -u has never been supported, but it was mentioned since 0a2bb55 (git ls-remote: make usage string match manpage - 2008-11-11). Nobody has complained about it for seven years, it's probably safe to say nobody cares. So let's remove "-u" in documents instead of adding code to support it. While at there, fix --upload-pack syntax too. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-ls-remote.txt | 3 +-- builtin/ls-remote.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index 2e22915eb857bd..d510c05e11d3cb 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -9,7 +9,7 @@ git-ls-remote - List references in a remote repository SYNOPSIS -------- [verse] -'git ls-remote' [--heads] [--tags] [-u | --upload-pack ] +'git ls-remote' [--heads] [--tags] [--upload-pack=] [--exit-code] [...] DESCRIPTION @@ -29,7 +29,6 @@ OPTIONS both, references stored in refs/heads and refs/tags are displayed. --u :: --upload-pack=:: Specify the full path of 'git-upload-pack' on the remote host. This allows listing references from repositories accessed via diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 4554dbc8a98c0d..5e9d5450b79713 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -4,7 +4,7 @@ #include "remote.h" static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u | --upload-pack ]\n" +"git ls-remote [--heads] [--tags] [--upload-pack=]\n" " [-q | --quiet] [--exit-code] [--get-url] [ [...]]"; /* From 3efb988098858bf6b974b1e673a190f9d2965d1d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 19:12:23 -0400 Subject: [PATCH 180/539] react to errors in xdi_diff When we call into xdiff to perform a diff, we generally lose the return code completely. Typically by ignoring the return of our xdi_diff wrapper, but sometimes we even propagate that return value up and then ignore it later. This can lead to us silently producing incorrect diffs (e.g., "git log" might produce no output at all, not even a diff header, for a content-level diff). In practice this does not happen very often, because the typical reason for xdiff to report failure is that it malloc() failed (it uses straight malloc, and not our xmalloc wrapper). But it could also happen when xdiff triggers one our callbacks, which returns an error (e.g., outf() in builtin/rerere.c tries to report a write failure in this way). And the next patch also plans to add more failure modes. Let's notice an error return from xdiff and react appropriately. In most of the diff.c code, we can simply die(), which matches the surrounding code (e.g., that is what we do if we fail to load a file for diffing in the first place). This is not that elegant, but we are probably better off dying to let the user know there was a problem, rather than simply generating bogus output. We could also just die() directly in xdi_diff, but the callers typically have a bit more context, and can provide a better message (and if we do later decide to pass errors up, we're one step closer to doing so). There is one interesting case, which is in diff_grep(). Here if we cannot generate the diff, there is nothing to match, and we silently return "no hits". This is actually what the existing code does already, but we make it a little more explicit. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/blame.c | 9 +++++++-- builtin/merge-tree.c | 3 ++- builtin/rerere.c | 10 ++++++---- combine-diff.c | 6 ++++-- diff.c | 26 ++++++++++++++++---------- diffcore-pickaxe.c | 4 ++-- line-log.c | 7 ++++--- 7 files changed, 41 insertions(+), 24 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index 2b1f9dd6cd01be..66b4fbf466539a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -972,7 +972,10 @@ static void pass_blame_to_parent(struct scoreboard *sb, fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; - diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d); + if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d)) + die("unable to generate diff (%s -> %s)", + sha1_to_hex(parent->commit->object.sha1), + sha1_to_hex(target->commit->object.sha1)); /* The rest are the same as the parent */ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent); *d.dstq = NULL; @@ -1118,7 +1121,9 @@ static void find_copy_in_blob(struct scoreboard *sb, * file_p partially may match that image. */ memset(split, 0, sizeof(struct blame_entry [3])); - diff_hunks(file_p, &file_o, 1, handle_split_cb, &d); + if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d)) + die("unable to generate diff (%s)", + sha1_to_hex(parent->commit->object.sha1)); /* remainder, if any, all match the preimage */ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index f9ab48597e58ed..2a4aafec6a906e 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -118,7 +118,8 @@ static void show_diff(struct merge_list *entry) if (!dst.ptr) size = 0; dst.size = size; - xdi_diff(&src, &dst, &xpp, &xecfg, &ecb); + if (xdi_diff(&src, &dst, &xpp, &xecfg, &ecb)) + die("unable to generate diff"); free(src.ptr); free(dst.ptr); } diff --git a/builtin/rerere.c b/builtin/rerere.c index 98eb8c5404914e..aab8f3b1f00fb9 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -29,9 +29,10 @@ static int diff_two(const char *file1, const char *label1, xdemitconf_t xecfg; xdemitcb_t ecb; mmfile_t minus, plus; + int ret; if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2)) - return 1; + return -1; printf("--- a/%s\n+++ b/%s\n", label1, label2); fflush(stdout); @@ -40,11 +41,11 @@ static int diff_two(const char *file1, const char *label1, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = outf; - xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); + ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); free(minus.ptr); free(plus.ptr); - return 0; + return ret; } int cmd_rerere(int argc, const char **argv, const char *prefix) @@ -104,7 +105,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const char *name = (const char *)merge_rr.items[i].util; - diff_two(rerere_path(name, "preimage"), path, path, path); + if (diff_two(rerere_path(name, "preimage"), path, path, path)) + die("unable to generate diff for %s", name); } else usage_with_options(rerere_usage, options); diff --git a/combine-diff.c b/combine-diff.c index 91edce58e15b82..284bec6ad50de7 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -419,8 +419,10 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, state.num_parent = num_parent; state.n = n; - xdi_diff_outf(&parent_file, result_file, consume_line, &state, - &xpp, &xecfg); + if (xdi_diff_outf(&parent_file, result_file, consume_line, &state, + &xpp, &xecfg)) + die("unable to generate combined diff for %s", + sha1_to_hex(parent)); free(parent_file.ptr); /* Assign line numbers for this parent. diff --git a/diff.c b/diff.c index 7500c5509550cc..6bbf28bff2c8b0 100644 --- a/diff.c +++ b/diff.c @@ -1002,8 +1002,9 @@ static void diff_words_show(struct diff_words_data *diff_words) xpp.flags = 0; /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; - xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, - &xpp, &xecfg); + if (xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, + &xpp, &xecfg)) + die("unable to generate word diff"); free(minus.ptr); free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + @@ -2400,8 +2401,9 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = strtoul(v, NULL, 10); if (o->word_diff) init_diff_words_data(&ecbdata, o, one, two); - xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, - &xpp, &xecfg); + if (xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, + &xpp, &xecfg)) + die("unable to generate diff for %s", one->path); if (o->word_diff) free_diff_words_data(&ecbdata); if (textconv_one) @@ -2478,8 +2480,9 @@ static void builtin_diffstat(const char *name_a, const char *name_b, xpp.flags = o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; - xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg); + if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, + &xpp, &xecfg)) + die("unable to generate diffstat for %s", one->path); } diff_free_filespec_data(one); @@ -2525,8 +2528,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = 0; - xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, - &xpp, &xecfg); + if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, + &xpp, &xecfg)) + die("unable to generate checkdiff for %s", one->path); if (data.ws_rule & WS_BLANK_AT_EOF) { struct emit_callback ecbdata; @@ -4425,8 +4429,10 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = 0; - xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, - &xpp, &xecfg); + if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, + &xpp, &xecfg)) + return error("unable to generate patch-id diff for %s", + p->one->path); } git_SHA1_Final(sha1, &ctx); diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index 185f86b2840d33..7715c13ec4780a 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -62,8 +62,8 @@ static int diff_grep(mmfile_t *one, mmfile_t *two, ecbdata.hit = 0; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; - xdi_diff_outf(one, two, diffgrep_consume, &ecbdata, - &xpp, &xecfg); + if (xdi_diff_outf(one, two, diffgrep_consume, &ecbdata, &xpp, &xecfg)) + return 0; return ecbdata.hit; } diff --git a/line-log.c b/line-log.c index 1a6bc5921b4890..d4e29a574f1fdb 100644 --- a/line-log.c +++ b/line-log.c @@ -325,7 +325,7 @@ static int collect_diff_cb(long start_a, long count_a, return 0; } -static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out) +static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out) { struct collect_diff_cbdata cbdata = {NULL}; xpparam_t xpp; @@ -340,7 +340,7 @@ static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges xecfg.hunk_func = collect_diff_cb; memset(&ecb, 0, sizeof(ecb)); ecb.priv = &cbdata; - xdi_diff(parent, target, &xpp, &xecfg, &ecb); + return xdi_diff(parent, target, &xpp, &xecfg, &ecb); } /* @@ -1030,7 +1030,8 @@ static int process_diff_filepair(struct rev_info *rev, } diff_ranges_init(&diff); - collect_diff(&file_parent, &file_target, &diff); + if (collect_diff(&file_parent, &file_target, &diff)) + die("unable to generate diff for %s", pair->one->path); /* NEEDSWORK should apply some heuristics to prevent mismatches */ free(rg->path); From dcd1742e56ebb944c4ff62346da4548e1e3be675 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 24 Sep 2015 19:12:45 -0400 Subject: [PATCH 181/539] xdiff: reject files larger than ~1GB The xdiff code is not prepared to handle extremely large files. It uses "int" in many places, which can overflow if we have a very large number of lines or even bytes in our input files. This can cause us to produce incorrect diffs, with no indication that the output is wrong. Or worse, we may even underallocate a buffer whose size is the result of an overflowing addition. We're much better off to tell the user that we cannot diff or merge such a large file. This patch covers both cases, but in slightly different ways: 1. For merging, we notice the large file and cleanly fall back to a binary merge (which is effectively "we cannot merge this"). 2. For diffing, we make the binary/text distinction much earlier, and in many different places. For this case, we'll use the xdi_diff as our choke point, and reject any diff there before it hits the xdiff code. This means in most cases we'll die() immediately after. That's not ideal, but in practice we shouldn't generally hit this code path unless the user is trying to do something tricky. We already consider files larger than core.bigfilethreshold to be binary, so this code would only kick in when that is circumvented (either by bumping that value, or by using a .gitattribute to mark a file as diffable). In other words, we can avoid being "nice" here, because there is already nice code that tries to do the right thing. We are adding the suspenders to the nice code's belt, so notice when it has been worked around (both to protect the user from malicious inputs, and because it is better to die() than generate bogus output). The maximum size was chosen after experimenting with feeding large files to the xdiff code. It's just under a gigabyte, which leaves room for two obvious cases: - a diff3 merge conflict result on files of maximum size X could be 3*X plus the size of the markers, which would still be only about 3G, which fits in a 32-bit int. - some of the diff code allocates arrays of one int per record. Even if each file consists only of blank lines, then a file smaller than 1G will have fewer than 1G records, and therefore the int array will fit in 4G. Since the limit is arbitrary anyway, I chose to go under a gigabyte, to leave a safety margin (e.g., we would not want to overflow by allocating "(records + 1) * sizeof(int)" or similar. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ll-merge.c | 5 ++++- xdiff-interface.c | 3 +++ xdiff-interface.h | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ll-merge.c b/ll-merge.c index 8ea03e536a5665..4e789f533043c7 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -88,7 +88,10 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, xmparam_t xmp; assert(opts); - if (buffer_is_binary(orig->ptr, orig->size) || + if (orig->size > MAX_XDIFF_SIZE || + src1->size > MAX_XDIFF_SIZE || + src2->size > MAX_XDIFF_SIZE || + buffer_is_binary(orig->ptr, orig->size) || buffer_is_binary(src1->ptr, src1->size) || buffer_is_binary(src2->ptr, src2->size)) { return ll_binary_merge(drv_unused, result, diff --git a/xdiff-interface.c b/xdiff-interface.c index ecfa05f616f4b7..cb67c1c42b35e4 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -131,6 +131,9 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co mmfile_t a = *mf1; mmfile_t b = *mf2; + if (mf1->size > MAX_XDIFF_SIZE || mf2->size > MAX_XDIFF_SIZE) + return -1; + trim_common_tail(&a, &b, xecfg->ctxlen); return xdl_diff(&a, &b, xpp, xecfg, xecb); diff --git a/xdiff-interface.h b/xdiff-interface.h index eff7762ee1a1bb..fbb5a1c3949b6e 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -3,6 +3,13 @@ #include "xdiff/xdiff.h" +/* + * xdiff isn't equipped to handle content over a gigabyte; + * we make the cutoff 1GB - 1MB to give some breathing + * room for constant-sized additions (e.g., merge markers) + */ +#define MAX_XDIFF_SIZE (1024UL * 1024 * 1023) + typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long); int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); From 83c4d380171a2ecd24dd2e04072692ec54a7aaa5 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 25 Sep 2015 17:58:09 -0400 Subject: [PATCH 182/539] merge-file: enforce MAX_XDIFF_SIZE on incoming files The previous commit enforces MAX_XDIFF_SIZE at the interfaces to xdiff: xdi_diff (which calls xdl_diff) and ll_xdl_merge (which calls xdl_merge). But we have another direct call to xdl_merge in merge-file.c. If it were written today, this probably would just use the ll_merge machinery. But it predates that code, and uses slightly different options to xdl_merge (e.g., ZEALOUS_ALNUM). We could try to abstract out an xdi_merge to match the existing xdi_diff, but even that is difficult. Rather than simply report error, we try to treat large files as binary, and that distinction would happen outside of xdi_merge. The simplest fix is to just replicate the MAX_XDIFF_SIZE check in merge-file.c. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/merge-file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 232b76857cf4f6..04ae36a4528848 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -75,7 +75,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) names[i] = argv[i]; if (read_mmfile(mmfs + i, fname)) return -1; - if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) + if (mmfs[i].size > MAX_XDIFF_SIZE || + buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s", argv[i]); } From 18b58f707fdb3ad7d3d4931bd40693834c9ec8a0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 28 Sep 2015 15:00:37 -0700 Subject: [PATCH 183/539] Git 2.3.10 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.3.10.txt | 18 ++++++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes/2.3.10.txt diff --git a/Documentation/RelNotes/2.3.10.txt b/Documentation/RelNotes/2.3.10.txt new file mode 100644 index 00000000000000..9d425d814ddf36 --- /dev/null +++ b/Documentation/RelNotes/2.3.10.txt @@ -0,0 +1,18 @@ +Git v2.3.10 Release Notes +========================= + +Fixes since v2.3.9 +------------------ + + * xdiff code we use to generate diffs is not prepared to handle + extremely large files. It uses "int" in many places, which can + overflow if we have a very large number of lines or even bytes in + our input files, for example. Cap the input size to soemwhere + around 1GB for now. + + * Some protocols (like git-remote-ext) can execute arbitrary code + found in the URL. The URLs that submodules use may come from + arbitrary sources (e.g., .gitmodules files in a remote + repository), and can hurt those who blindly enable recursive + fetch. Restrict the allowed protocols to well known and safe + ones. diff --git a/Documentation/git.txt b/Documentation/git.txt index 41a09cac7af072..c06b9239698480 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.3.9/git.html[documentation for release 2.3.9] +* link:v2.3.10/git.html[documentation for release 2.3.10] * release notes for + link:RelNotes/2.3.10.txt[2.3.10], link:RelNotes/2.3.9.txt[2.3.9], link:RelNotes/2.3.8.txt[2.3.8], link:RelNotes/2.3.7.txt[2.3.7], diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 7577f66e97b2ff..0e353e80cc731a 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.3.9 +DEF_VER=v2.3.10 LF=' ' diff --git a/RelNotes b/RelNotes index 2f2927a251b887..25d9228ac5efe2 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.3.9.txt \ No newline at end of file +Documentation/RelNotes/2.3.10.txt \ No newline at end of file From a2558fb8e1e387b630312311e1d22c95663da5d0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 28 Sep 2015 15:29:54 -0700 Subject: [PATCH 184/539] Git 2.4.10 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.4.10.txt | 18 ++++++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes/2.4.10.txt diff --git a/Documentation/RelNotes/2.4.10.txt b/Documentation/RelNotes/2.4.10.txt new file mode 100644 index 00000000000000..8621199bc67171 --- /dev/null +++ b/Documentation/RelNotes/2.4.10.txt @@ -0,0 +1,18 @@ +Git v2.4.10 Release Notes +========================= + +Fixes since v2.4.9 +------------------ + + * xdiff code we use to generate diffs is not prepared to handle + extremely large files. It uses "int" in many places, which can + overflow if we have a very large number of lines or even bytes in + our input files, for example. Cap the input size to soemwhere + around 1GB for now. + + * Some protocols (like git-remote-ext) can execute arbitrary code + found in the URL. The URLs that submodules use may come from + arbitrary sources (e.g., .gitmodules files in a remote + repository), and can hurt those who blindly enable recursive + fetch. Restrict the allowed protocols to well known and safe + ones. diff --git a/Documentation/git.txt b/Documentation/git.txt index 5826bd97711d61..a405e4f2e70942 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.4.9/git.html[documentation for release 2.4.9] +* link:v2.4.10/git.html[documentation for release 2.4.10] * release notes for + link:RelNotes/2.4.10.txt[2.4.10], link:RelNotes/2.4.9.txt[2.4.9], link:RelNotes/2.4.8.txt[2.4.8], link:RelNotes/2.4.7.txt[2.4.7], diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 38e296b311bfe9..859e14c1df649e 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.4.9 +DEF_VER=v2.4.10 LF=' ' diff --git a/RelNotes b/RelNotes index 3e8a59f846b1c5..50e4b25973e301 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.4.9.txt \ No newline at end of file +Documentation/RelNotes/2.4.10.txt \ No newline at end of file From 24358560c3c0ab51c9ef8178d99f46711716f6c0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 28 Sep 2015 15:26:49 -0700 Subject: [PATCH 185/539] Git 2.5.4 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.5.4.txt | 18 ++++++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes/2.5.4.txt diff --git a/Documentation/RelNotes/2.5.4.txt b/Documentation/RelNotes/2.5.4.txt new file mode 100644 index 00000000000000..a5e8477a4a6bee --- /dev/null +++ b/Documentation/RelNotes/2.5.4.txt @@ -0,0 +1,18 @@ +Git v2.5.4 Release Notes +======================== + +Fixes since v2.5.4 +------------------ + + * xdiff code we use to generate diffs is not prepared to handle + extremely large files. It uses "int" in many places, which can + overflow if we have a very large number of lines or even bytes in + our input files, for example. Cap the input size to soemwhere + around 1GB for now. + + * Some protocols (like git-remote-ext) can execute arbitrary code + found in the URL. The URLs that submodules use may come from + arbitrary sources (e.g., .gitmodules files in a remote + repository), and can hurt those who blindly enable recursive + fetch. Restrict the allowed protocols to well known and safe + ones. diff --git a/Documentation/git.txt b/Documentation/git.txt index b2d88688aa0fe0..1a262757dc9f73 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.5.3/git.html[documentation for release 2.5.3] +* link:v2.5.4/git.html[documentation for release 2.5.4] * release notes for + link:RelNotes/2.5.4.txt[2.5.4], link:RelNotes/2.5.3.txt[2.5.3], link:RelNotes/2.5.2.txt[2.5.2], link:RelNotes/2.5.1.txt[2.5.1], diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 7915077d2a0d67..998786939b9b64 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.5.3 +DEF_VER=v2.5.4 LF=' ' diff --git a/RelNotes b/RelNotes index 01910c524602b7..cdb1b4853d8267 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.5.3.txt \ No newline at end of file +Documentation/RelNotes/2.5.4.txt \ No newline at end of file From 22f698cb188243b313e024d618283e0293e37140 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 28 Sep 2015 19:19:27 -0700 Subject: [PATCH 186/539] Git 2.6.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.6.1.txt | 18 ++++++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes/2.6.1.txt diff --git a/Documentation/RelNotes/2.6.1.txt b/Documentation/RelNotes/2.6.1.txt new file mode 100644 index 00000000000000..1e51363e3c107a --- /dev/null +++ b/Documentation/RelNotes/2.6.1.txt @@ -0,0 +1,18 @@ +Git v2.6.1 Release Notes +======================== + +Fixes since v2.6 +---------------- + + * xdiff code we use to generate diffs is not prepared to handle + extremely large files. It uses "int" in many places, which can + overflow if we have a very large number of lines or even bytes in + our input files, for example. Cap the input size to soemwhere + around 1GB for now. + + * Some protocols (like git-remote-ext) can execute arbitrary code + found in the URL. The URLs that submodules use may come from + arbitrary sources (e.g., .gitmodules files in a remote + repository), and can hurt those who blindly enable recursive + fetch. Restrict the allowed protocols to well known and safe + ones. diff --git a/Documentation/git.txt b/Documentation/git.txt index f9a7fb94c5becf..1a426311175941 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.6.0/git.html[documentation for release 2.6] +* link:v2.6.1/git.html[documentation for release 2.6.1] * release notes for + link:RelNotes/2.6.1.txt[2.6.1], link:RelNotes/2.6.0.txt[2.6]. * link:v2.5.4/git.html[documentation for release 2.5.4] diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 97f58d1dbb5fa4..e1aba8533f5b6d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.6.0 +DEF_VER=v2.6.1 LF=' ' diff --git a/RelNotes b/RelNotes index 84a20c84c648ca..def6ebd43038cd 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.6.0.txt \ No newline at end of file +Documentation/RelNotes/2.6.1.txt \ No newline at end of file From 82aa9b751fe96c5e55c36819aedea3d47e98bb57 Mon Sep 17 00:00:00 2001 From: Dimitriy Ryazantcev Date: Wed, 30 Sep 2015 18:01:23 +0300 Subject: [PATCH 187/539] l10n: ru.po: update Russian translation Signed-off-by: Dimitriy Ryazantcev --- po/ru.po | 3550 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 1967 insertions(+), 1583 deletions(-) diff --git a/po/ru.po b/po/ru.po index 4f66cee3468702..c9e1fb8e0d61e1 100644 --- a/po/ru.po +++ b/po/ru.po @@ -10,10 +10,10 @@ msgid "" msgstr "" "Project-Id-Version: Перевод Git на русский язык\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2015-07-14 07:19+0800\n" -"PO-Revision-Date: 2015-07-14 13:06+0000\n" +"POT-Creation-Date: 2015-09-15 06:45+0800\n" +"PO-Revision-Date: 2015-09-30 14:53+0000\n" "Last-Translator: Dimitriy Ryazantcev \n" -"Language-Team: Russian (http://www.transifex.com/p/git-po-ru/language/ru/)\n" +"Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -31,94 +31,106 @@ msgid "" "as appropriate to mark resolution and make a commit." msgstr "Исправьте их в рабочем каталоге, затем запустите «git add/rm <файл>»,\nчтобы пометить исправление и сделайте коммит." -#: archive.c:11 +#: advice.c:101 builtin/merge.c:1227 +msgid "You have not concluded your merge (MERGE_HEAD exists)." +msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD)." + +#: advice.c:103 +msgid "Please, commit your changes before you can merge." +msgstr "Выполните коммит ваших изменений, перед слиянием." + +#: advice.c:104 +msgid "Exiting because of unfinished merge." +msgstr "Выход из-за незавершенного слияния." + +#: archive.c:12 msgid "git archive [] [...]" msgstr "git archive [<опции>] <указатель-дерева> [<путь>…]" -#: archive.c:12 +#: archive.c:13 msgid "git archive --list" msgstr "git archive --list" -#: archive.c:13 +#: archive.c:14 msgid "" "git archive --remote [--exec ] [] " "[...]" msgstr "git archive --remote <репозиторий> [--exec <команда>] [<опции>] <указатель-дерева> [<путь>…]" -#: archive.c:14 +#: archive.c:15 msgid "git archive --remote [--exec ] --list" msgstr "git archive --remote <репозиторий> [--exec <команда>] --list" -#: archive.c:342 builtin/add.c:137 builtin/add.c:428 builtin/rm.c:327 +#: archive.c:343 builtin/add.c:137 builtin/add.c:426 builtin/rm.c:327 #, c-format msgid "pathspec '%s' did not match any files" msgstr "спецификация пути «%s» не соответствует ни одному файлу" -#: archive.c:427 +#: archive.c:428 msgid "fmt" msgstr "формат" -#: archive.c:427 +#: archive.c:428 msgid "archive format" msgstr "формат архива" -#: archive.c:428 builtin/log.c:1204 +#: archive.c:429 builtin/log.c:1229 msgid "prefix" msgstr "префикс" -#: archive.c:429 +#: archive.c:430 msgid "prepend prefix to each pathname in the archive" msgstr "добавлять префикс перед каждым путем файла в архиве" -#: archive.c:430 builtin/archive.c:88 builtin/blame.c:2516 -#: builtin/blame.c:2517 builtin/config.c:57 builtin/fast-export.c:986 -#: builtin/fast-export.c:988 builtin/grep.c:712 builtin/hash-object.c:99 -#: builtin/ls-files.c:446 builtin/ls-files.c:449 builtin/notes.c:394 -#: builtin/notes.c:557 builtin/read-tree.c:109 parse-options.h:150 +#: archive.c:431 builtin/archive.c:88 builtin/blame.c:2516 +#: builtin/blame.c:2517 builtin/config.c:58 builtin/fast-export.c:987 +#: builtin/fast-export.c:989 builtin/grep.c:712 builtin/hash-object.c:99 +#: builtin/ls-files.c:446 builtin/ls-files.c:449 builtin/notes.c:395 +#: builtin/notes.c:558 builtin/read-tree.c:109 parse-options.h:153 msgid "file" msgstr "файл" -#: archive.c:431 builtin/archive.c:89 +#: archive.c:432 builtin/archive.c:89 msgid "write the archive to this file" msgstr "запись архива в этот файл" -#: archive.c:433 +#: archive.c:434 msgid "read .gitattributes in working directory" msgstr "читать .gitattributes в рабочем каталоге" -#: archive.c:434 +#: archive.c:435 msgid "report archived files on stderr" msgstr "отчет об архивированных файлах в stderr" -#: archive.c:435 +#: archive.c:436 msgid "store only" msgstr "только хранение" -#: archive.c:436 +#: archive.c:437 msgid "compress faster" msgstr "сжимать быстрее" -#: archive.c:444 +#: archive.c:445 msgid "compress better" msgstr "сжимать лучше" -#: archive.c:447 +#: archive.c:448 msgid "list supported archive formats" msgstr "перечислить поддерживаемые форматы архивов" -#: archive.c:449 builtin/archive.c:90 builtin/clone.c:77 +#: archive.c:450 builtin/archive.c:90 builtin/clone.c:77 msgid "repo" msgstr "репозиторий" -#: archive.c:450 builtin/archive.c:91 +#: archive.c:451 builtin/archive.c:91 msgid "retrieve the archive from remote repository " msgstr "получить архив из внешнего <репозитория>" -#: archive.c:451 builtin/archive.c:92 builtin/notes.c:478 +#: archive.c:452 builtin/archive.c:92 builtin/notes.c:479 msgid "command" msgstr "комманда" -#: archive.c:452 builtin/archive.c:93 +#: archive.c:453 builtin/archive.c:93 msgid "path to the remote git-upload-archive command" msgstr "путь к команде git-upload-archive на машине с внешним репозиторием" @@ -230,6 +242,11 @@ msgstr "Неоднозначное имя объекта: «%s»." msgid "Not a valid branch point: '%s'." msgstr "Недопустимая точка ветки: «%s»." +#: branch.c:399 +#, c-format +msgid "'%s' is already checked out at '%s'" +msgstr "«%s» уже находится на «%s»" + #: bundle.c:34 #, c-format msgid "'%s' does not look like a v2 bundle file" @@ -240,7 +257,7 @@ msgstr "«%s» не похож на файл пакета версии 2" msgid "unrecognized header: %s%s (%d)" msgstr "неопознанный заголовок: %s%s (%d)" -#: bundle.c:87 builtin/commit.c:766 +#: bundle.c:87 builtin/commit.c:765 #, c-format msgid "could not open '%s'" msgstr "не удалось открыть «%s»" @@ -249,9 +266,9 @@ msgstr "не удалось открыть «%s»" msgid "Repository lacks these prerequisite commits:" msgstr "В репозитории отсутствуют необходимые коммиты:" -#: bundle.c:163 sequencer.c:650 sequencer.c:1105 builtin/blame.c:2705 -#: builtin/branch.c:651 builtin/commit.c:1045 builtin/log.c:330 -#: builtin/log.c:825 builtin/log.c:1432 builtin/log.c:1666 builtin/merge.c:358 +#: bundle.c:163 sequencer.c:636 sequencer.c:1083 builtin/blame.c:2708 +#: builtin/branch.c:652 builtin/commit.c:1044 builtin/log.c:334 +#: builtin/log.c:850 builtin/log.c:1457 builtin/log.c:1690 builtin/merge.c:358 #: builtin/shortlog.c:158 msgid "revision walk setup failed" msgstr "сбой инициализации прохода по редакциям" @@ -278,38 +295,38 @@ msgstr[1] "Пакет требует эти %d ссылки:" msgstr[2] "Пакет требует эти %d ссылок:" msgstr[3] "Пакет требует эти %d ссылок:" -#: bundle.c:251 +#: bundle.c:253 msgid "Could not spawn pack-objects" msgstr "Не удалось создать объекты пакета" -#: bundle.c:269 +#: bundle.c:264 msgid "pack-objects died" msgstr "критическая ошибка pack-objects" -#: bundle.c:309 +#: bundle.c:304 msgid "rev-list died" msgstr "критическая ошибка rev-list" -#: bundle.c:358 +#: bundle.c:353 #, c-format msgid "ref '%s' is excluded by the rev-list options" msgstr "ссылка «%s» исключена в соответствии с опциями rev-list" -#: bundle.c:437 builtin/log.c:153 builtin/log.c:1342 builtin/shortlog.c:261 +#: bundle.c:443 builtin/log.c:157 builtin/log.c:1367 builtin/shortlog.c:261 #, c-format msgid "unrecognized argument: %s" msgstr "неопознанный аргумент: %s" -#: bundle.c:443 +#: bundle.c:449 msgid "Refusing to create empty bundle." msgstr "Отклонение создания пустого пакета." -#: bundle.c:453 +#: bundle.c:459 #, c-format msgid "cannot create '%s'" msgstr "не удалось создать «%s»" -#: bundle.c:474 +#: bundle.c:480 msgid "index-pack died" msgstr "критическая ошибка index-pack" @@ -318,7 +335,8 @@ msgstr "критическая ошибка index-pack" msgid "invalid color value: %.*s" msgstr "недопустимое значение цвета: %.*s" -#: commit.c:40 +#: commit.c:40 builtin/am.c:451 builtin/am.c:487 builtin/am.c:1516 +#: builtin/am.c:2128 #, c-format msgid "could not parse %s" msgstr "не удалось разобрать %s" @@ -494,75 +512,75 @@ msgstr "сбой чтения orderfile «%s»" msgid "Performing inexact rename detection" msgstr "Выполняется неточное определение переименования" -#: diff.c:114 +#: diff.c:116 #, c-format msgid " Failed to parse dirstat cut-off percentage '%s'\n" msgstr " Сбой разбора величины среза (cut-off) у dirstat «%s»\n" -#: diff.c:119 +#: diff.c:121 #, c-format msgid " Unknown dirstat parameter '%s'\n" msgstr "Неизвестный параметр dirstat: «%s»\n" -#: diff.c:214 +#: diff.c:216 #, c-format msgid "Unknown value for 'diff.submodule' config variable: '%s'" msgstr "Неизвестное значения для переменной «diff.submodule»: «%s»" -#: diff.c:266 +#: diff.c:268 #, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" msgstr "Найдены ошибки в переменной «diff.dirstat»:\n%s" -#: diff.c:2997 +#: diff.c:2998 #, c-format msgid "external diff died, stopping at %s" msgstr "критическая ошибка при внешнем сравнении, останов на %s" -#: diff.c:3393 +#: diff.c:3394 msgid "--follow requires exactly one pathspec" msgstr "--follow требует ровно одной спецификации пути" -#: diff.c:3556 +#: diff.c:3557 #, c-format msgid "" "Failed to parse --dirstat/-X option parameter:\n" "%s" msgstr "Сбой разбора параметра опции --dirstat/-X :\n%s" -#: diff.c:3570 +#: diff.c:3571 #, c-format msgid "Failed to parse --submodule option parameter: '%s'" msgstr "Сбой разбора параметра опции --submodule: «%s»" -#: dir.c:1852 +#: dir.c:1853 msgid "failed to get kernel name and information" msgstr "не удалось получить имя ядра и информацию" -#: dir.c:1945 +#: dir.c:1936 msgid "Untracked cache is disabled on this system." msgstr "Кэш неотслеживаемых файлов отключен на этой системе." -#: gpg-interface.c:129 gpg-interface.c:200 +#: gpg-interface.c:166 gpg-interface.c:237 msgid "could not run gpg." msgstr "не удалось запустить gpg." -#: gpg-interface.c:141 +#: gpg-interface.c:178 msgid "gpg did not accept the data" msgstr "gpg не принял данные" -#: gpg-interface.c:152 +#: gpg-interface.c:189 msgid "gpg failed to sign the data" msgstr "gpg не удалось подписать данные" -#: gpg-interface.c:185 +#: gpg-interface.c:222 #, c-format msgid "could not create temporary file '%s': %s" msgstr "не удалось создать временный файл «%s»: %s" -#: gpg-interface.c:188 +#: gpg-interface.c:225 #, c-format msgid "failed writing detached signature to '%s': %s" msgstr "сбой записи отсоединенной подписи в «%s»: %s" @@ -640,20 +658,12 @@ msgstr[3] "\nВозможно, вы имели в виду что-то из эт msgid "%s: %s - %s" msgstr "%s: %s — %s" -#: lockfile.c:345 -msgid "BUG: reopen a lockfile that is still open" -msgstr "БАГ: повторное открытие файла блокировки, который уже открыт" - -#: lockfile.c:347 -msgid "BUG: reopen a lockfile that has been committed" -msgstr "БАГ: повторное открытие файла блокировки, который уже был закоммичен" - #: merge.c:41 msgid "failed to read the cache" msgstr "сбой чтения кэша" -#: merge.c:94 builtin/checkout.c:376 builtin/checkout.c:587 -#: builtin/clone.c:647 +#: merge.c:94 builtin/am.c:2001 builtin/am.c:2036 builtin/checkout.c:375 +#: builtin/checkout.c:586 builtin/clone.c:715 msgid "unable to write new index file" msgstr "не удалось записать новый файл индекса" @@ -700,7 +710,7 @@ msgstr "невозможно прочитать объект %s «%s»" msgid "blob expected for %s '%s'" msgstr "ожидается двоичный объект для %s «%s»" -#: merge-recursive.c:788 builtin/clone.c:306 +#: merge-recursive.c:788 builtin/clone.c:364 #, c-format msgid "failed to open '%s'" msgstr "не удалось открыть «%s»" @@ -908,19 +918,19 @@ msgstr "Не удается записать индекс." msgid "Cannot commit uninitialized/unreferenced notes tree" msgstr "Нельзя закоммитить неинициализированное или не имеющее ссылок дерево заметок" -#: notes-utils.c:82 +#: notes-utils.c:100 #, c-format msgid "Bad notes.rewriteMode value: '%s'" msgstr "Неправильное значение notes.rewriteMode: «%s»" -#: notes-utils.c:92 +#: notes-utils.c:110 #, c-format msgid "Refusing to rewrite notes in %s (outside of refs/notes/)" msgstr "Отказ в перезаписи заметок в %s (за пределами refs/notes/)" #. TRANSLATORS: The first %s is the name of the #. environment variable, the second %s is its value -#: notes-utils.c:119 +#: notes-utils.c:137 #, c-format msgid "Bad %s value: '%s'" msgstr "Неправильное значение переменной %s: «%s»" @@ -930,28 +940,28 @@ msgstr "Неправильное значение переменной %s: «%s msgid "unable to parse object: %s" msgstr "не удалось разобрать объект: %s" -#: parse-options.c:546 +#: parse-options.c:563 msgid "..." msgstr "…" -#: parse-options.c:564 +#: parse-options.c:581 #, c-format msgid "usage: %s" msgstr "использование: %s" #. TRANSLATORS: the colon here should align with the #. one in "usage: %s" translation -#: parse-options.c:568 +#: parse-options.c:585 #, c-format msgid " or: %s" msgstr " или: %s" -#: parse-options.c:571 +#: parse-options.c:588 #, c-format msgid " %s" msgstr " %s" -#: parse-options.c:605 +#: parse-options.c:622 msgid "-NUM" msgstr "-КОЛИЧЕСТВО" @@ -1015,7 +1025,7 @@ msgid "" "Perhaps you forgot to add either ':/' or '.' ?" msgstr "Не указан шаблон для исключения с помощью :(exclude).\nВозможно, вы забыли «:/» или «.» ?" -#: pretty.c:968 +#: pretty.c:969 msgid "unable to parse --pretty format" msgstr "не удалось разобрать формат для --pretty" @@ -1023,20 +1033,45 @@ msgstr "не удалось разобрать формат для --pretty" msgid "done" msgstr "готово" -#: read-cache.c:1295 +#: read-cache.c:1296 #, c-format msgid "" "index.version set, but the value is invalid.\n" "Using version %i" msgstr "index.version указан, но значение недействительное.\nИспользую версию %i" -#: read-cache.c:1305 +#: read-cache.c:1306 #, c-format msgid "" "GIT_INDEX_VERSION set, but the value is invalid.\n" "Using version %i" msgstr "GIT_INDEX_VERSION указан, но значение недействительное.\nИспользую версию %i" +#: refs.c:2941 builtin/merge.c:760 builtin/merge.c:871 builtin/merge.c:973 +#: builtin/merge.c:983 +#, c-format +msgid "Could not open '%s' for writing" +msgstr "Не удалось открыть «%s» для записи" + +#: refs.c:3001 +#, c-format +msgid "could not delete reference %s: %s" +msgstr "не удалось удалить ссылку %s: %s" + +#: refs.c:3004 +#, c-format +msgid "could not delete references: %s" +msgstr "не удалось удалить ссылки: %s" + +#: refs.c:3013 +#, c-format +msgid "could not remove reference %s" +msgstr "не удалось удалить ссылки %s" + +#: ref-filter.c:660 +msgid "unable to parse format" +msgstr "не удалось разобрать формат" + #: remote.c:792 #, c-format msgid "Cannot fetch both %s and %s to %s" @@ -1156,7 +1191,16 @@ msgstr[3] "Ваша ветка и «%s» разошлись\nи теперь и msgid " (use \"git pull\" to merge the remote branch into yours)\n" msgstr " (используйте «git pull», чтобы слить внешнюю ветку в вашу)\n" -#: revision.c:2366 +#: revision.c:2198 +msgid "your current branch appears to be broken" +msgstr "похоже, ваша текущая ветка повреждена" + +#: revision.c:2201 +#, c-format +msgid "your current branch '%s' does not have any commits yet" +msgstr "ваша текущая ветка «%s» еще не содержит ни одного коммита" + +#: revision.c:2395 msgid "--first-parent is incompatible with --bisect" msgstr "опцию --first-parent нельзя использовать одновременно с --bisect" @@ -1169,257 +1213,251 @@ msgstr "сбой открытия /dev/null" msgid "dup2(%d,%d) failed" msgstr "dup2(%d,%d) сбой" -#: send-pack.c:272 +#: send-pack.c:295 msgid "failed to sign the push certificate" msgstr "сбой подписания сертификата отправки" -#: send-pack.c:378 +#: send-pack.c:404 msgid "the receiving end does not support --signed push" msgstr "принимающая сторона не поддерживает отправку с опцией --signed" -#: send-pack.c:389 +#: send-pack.c:406 +msgid "" +"not sending a push certificate since the receiving end does not support " +"--signed push" +msgstr "не отправляем сертификат для отправки, так как принимающая сторона не поддерживает отправку с опцией --signed" + +#: send-pack.c:418 msgid "the receiving end does not support --atomic push" msgstr "принимающая сторона не поддерживает отправку с опцией --atomic" -#: sequencer.c:172 builtin/merge.c:760 builtin/merge.c:871 builtin/merge.c:973 -#: builtin/merge.c:983 -#, c-format -msgid "Could not open '%s' for writing" -msgstr "Не удалось открыть «%s» для записи" - -#: sequencer.c:174 builtin/merge.c:344 builtin/merge.c:763 builtin/merge.c:975 -#: builtin/merge.c:988 -#, c-format -msgid "Could not write to '%s'" -msgstr "Не удалось записать в «%s»" - -#: sequencer.c:195 +#: sequencer.c:183 msgid "" "after resolving the conflicts, mark the corrected paths\n" "with 'git add ' or 'git rm '" msgstr "после разрешения конфликтов, пометьте исправленные пути\nс помощью «git add <пути>» или «git rm <пути>»" -#: sequencer.c:198 +#: sequencer.c:186 msgid "" "after resolving the conflicts, mark the corrected paths\n" "with 'git add ' or 'git rm '\n" "and commit the result with 'git commit'" msgstr "после разрешения конфликтов, пометьте исправленные пути\nс помощью «git add <пути>» или «git rm <пути>»\nи сделайте коммит с помощью «git commit»" -#: sequencer.c:211 sequencer.c:861 sequencer.c:944 +#: sequencer.c:199 sequencer.c:842 sequencer.c:922 #, c-format msgid "Could not write to %s" msgstr "Не удалось записать в %s" -#: sequencer.c:214 +#: sequencer.c:202 #, c-format msgid "Error wrapping up %s" msgstr "Ошибка оборачивания %s" -#: sequencer.c:229 +#: sequencer.c:217 msgid "Your local changes would be overwritten by cherry-pick." msgstr "Ваши локальные изменение будут перезаписаны отбором лучшего." -#: sequencer.c:231 +#: sequencer.c:219 msgid "Your local changes would be overwritten by revert." msgstr "Ваши локальные изменение будут перезаписаны возвратом коммита." -#: sequencer.c:234 +#: sequencer.c:222 msgid "Commit your changes or stash them to proceed." msgstr "Сделайте коммит или спрячьте ваши изменения для продолжения." #. TRANSLATORS: %s will be "revert" or "cherry-pick" -#: sequencer.c:321 +#: sequencer.c:309 #, c-format msgid "%s: Unable to write new index file" msgstr "%s: Не удалось записать файл индекса" -#: sequencer.c:339 +#: sequencer.c:327 msgid "Could not resolve HEAD commit\n" msgstr "Не удалось определить HEAD коммит\n" -#: sequencer.c:359 +#: sequencer.c:347 msgid "Unable to update cache tree\n" msgstr "Не удалось обновить дерево кэша\n" -#: sequencer.c:411 +#: sequencer.c:399 #, c-format msgid "Could not parse commit %s\n" msgstr "Не удалось разобрать коммит %s\n" -#: sequencer.c:416 +#: sequencer.c:404 #, c-format msgid "Could not parse parent commit %s\n" msgstr "Не удалось разобрать родительскую коммит %s\n" -#: sequencer.c:482 +#: sequencer.c:469 msgid "Your index file is unmerged." msgstr "Ваш файл индекса не слит." -#: sequencer.c:501 +#: sequencer.c:488 #, c-format msgid "Commit %s is a merge but no -m option was given." msgstr "Коммит %s — это коммит-слияние, но опция -m не указана." -#: sequencer.c:509 +#: sequencer.c:496 #, c-format msgid "Commit %s does not have parent %d" msgstr "У коммита %s нет предка %d" -#: sequencer.c:513 +#: sequencer.c:500 #, c-format msgid "Mainline was specified but commit %s is not a merge." msgstr "Основная ветка указана, но коммит %s не является слиянием." #. TRANSLATORS: The first %s will be "revert" or #. "cherry-pick", the second %s a SHA1 -#: sequencer.c:526 +#: sequencer.c:513 #, c-format msgid "%s: cannot parse parent commit %s" msgstr "%s: не удалось разобрать родительский коммит для %s" -#: sequencer.c:530 +#: sequencer.c:517 #, c-format msgid "Cannot get commit message for %s" msgstr "Не удалось получить сообщение коммита для %s" -#: sequencer.c:616 +#: sequencer.c:603 #, c-format msgid "could not revert %s... %s" msgstr "не удалось возвратить коммит %s… %s" -#: sequencer.c:617 +#: sequencer.c:604 #, c-format msgid "could not apply %s... %s" msgstr "не удалось применить коммит %s… %s" -#: sequencer.c:653 +#: sequencer.c:639 msgid "empty commit set passed" msgstr "передан пустой набор коммитов" -#: sequencer.c:661 +#: sequencer.c:647 #, c-format msgid "git %s: failed to read the index" msgstr "git %s: сбой чтения индекса" -#: sequencer.c:665 +#: sequencer.c:651 #, c-format msgid "git %s: failed to refresh the index" msgstr "git %s: сбой обновления индекса" -#: sequencer.c:725 +#: sequencer.c:711 #, c-format msgid "Cannot %s during a %s" msgstr "Не удалось %s во время %s" -#: sequencer.c:747 +#: sequencer.c:733 #, c-format msgid "Could not parse line %d." msgstr "Не удалось разобрать строку %d." -#: sequencer.c:752 +#: sequencer.c:738 msgid "No commits parsed." msgstr "Коммиты не разобраны." -#: sequencer.c:765 +#: sequencer.c:750 #, c-format msgid "Could not open %s" msgstr "Не удалось открыть %s" -#: sequencer.c:769 +#: sequencer.c:754 #, c-format msgid "Could not read %s." msgstr "Не удалось прочитать %s." -#: sequencer.c:776 +#: sequencer.c:761 #, c-format msgid "Unusable instruction sheet: %s" msgstr "Непригодная для использования карта с инструкциями: %s" -#: sequencer.c:806 +#: sequencer.c:791 #, c-format msgid "Invalid key: %s" msgstr "Недействительный ключ: %s" -#: sequencer.c:809 +#: sequencer.c:794 builtin/pull.c:47 builtin/pull.c:49 #, c-format msgid "Invalid value for %s: %s" msgstr "Неправильное значение %s: %s" -#: sequencer.c:821 +#: sequencer.c:804 #, c-format msgid "Malformed options sheet: %s" msgstr "Испорченная карта с опциями: %s" -#: sequencer.c:842 +#: sequencer.c:823 msgid "a cherry-pick or revert is already in progress" msgstr "отбор лучшего или возврат коммита уже выполняется" -#: sequencer.c:843 +#: sequencer.c:824 msgid "try \"git cherry-pick (--continue | --quit | --abort)\"" msgstr "попробуйте «git cherry-pick (--continue | --quit | --abort)»" -#: sequencer.c:847 +#: sequencer.c:828 #, c-format msgid "Could not create sequencer directory %s" msgstr "Не удалось создать каталог для указателя следования коммитов %s" -#: sequencer.c:863 sequencer.c:948 +#: sequencer.c:844 sequencer.c:926 #, c-format msgid "Error wrapping up %s." msgstr "Ошибка оборачивания %s." -#: sequencer.c:882 sequencer.c:1018 +#: sequencer.c:863 sequencer.c:996 msgid "no cherry-pick or revert in progress" msgstr "отбор лучшего или возврат коммита не выполняется" -#: sequencer.c:884 +#: sequencer.c:865 msgid "cannot resolve HEAD" msgstr "не удалось определить HEAD" -#: sequencer.c:886 +#: sequencer.c:867 msgid "cannot abort from a branch yet to be born" msgstr "нельзя отменить изменения с ветки, которая еще не создана" -#: sequencer.c:908 builtin/apply.c:4291 +#: sequencer.c:887 builtin/apply.c:4291 #, c-format msgid "cannot open %s: %s" msgstr "не удалось открыть %s: %s" -#: sequencer.c:911 +#: sequencer.c:890 #, c-format msgid "cannot read %s: %s" msgstr "не удалось прочитать %s: %s" -#: sequencer.c:912 +#: sequencer.c:891 msgid "unexpected end of file" msgstr "неожиданный конец файла" -#: sequencer.c:918 +#: sequencer.c:897 #, c-format msgid "stored pre-cherry-pick HEAD file '%s' is corrupt" msgstr "сохраненный файл с HEAD перед отбором лучшего «%s» поврежден" -#: sequencer.c:941 +#: sequencer.c:919 #, c-format msgid "Could not format %s." msgstr "Не удалось отформатировать %s." -#: sequencer.c:1086 +#: sequencer.c:1064 #, c-format msgid "%s: can't cherry-pick a %s" msgstr "%s: не удалось отобрать %s" -#: sequencer.c:1089 +#: sequencer.c:1067 #, c-format msgid "%s: bad revision" msgstr "%s: плохая редакция" -#: sequencer.c:1123 +#: sequencer.c:1101 msgid "Can't revert as initial commit" msgstr "Нельзя возвратить изначальный коммит" -#: sequencer.c:1124 +#: sequencer.c:1102 msgid "Can't cherry-pick into empty head" msgstr "Нельзя отобрать лучшее в пустой HEAD" @@ -1441,30 +1479,30 @@ msgid "" "running \"git config advice.objectNameWarning false\"" msgstr "Обычно Git не создает ссылки, оканчивающиеся на 40 шестнадцатеричных\nсимволов, потому, что они будут игнорироваться, когда вы просто\nукажете это 40-символьное шестнадцатеричное число. Такие ссылки\nмогли быть созданы по ошибке. Например, с помощью:\n\n git checkout -b $br $(git rev-parse …)\n\n, если «$br» оказался пустым, то ссылка с 40-символьным\nшестнадцатеричным числом будет создана. Пожалуйста, просмотрите эти\nссылки и, возможно, удалите их. Вы можете отключить это сообщение\nзапустив «git config advice.objectNameWarning false»" -#: submodule.c:64 submodule.c:98 +#: submodule.c:61 submodule.c:95 msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first" msgstr "Не удалось изменить не слитый .gitmodules, сначала разрешите конфликты" -#: submodule.c:68 submodule.c:102 +#: submodule.c:65 submodule.c:99 #, c-format msgid "Could not find section in .gitmodules where path=%s" msgstr "Не удалось найти раздел в .gitmodules, где путь равен %s" -#: submodule.c:76 +#: submodule.c:73 #, c-format msgid "Could not update .gitmodules entry %s" msgstr " Не удалось обновить .gitmodules запись %s" -#: submodule.c:109 +#: submodule.c:106 #, c-format msgid "Could not remove .gitmodules entry for %s" msgstr "Не удалось удалить запись в .gitmodules для %s" -#: submodule.c:120 +#: submodule.c:117 msgid "staging updated .gitmodules failed" msgstr "сбой индексирования обновленного .gitmodules" -#: submodule.c:1115 +#: submodule.c:1045 #, c-format msgid "Could not set core.worktree in %s" msgstr "Не удалось установить core.worktree в %s" @@ -1494,6 +1532,11 @@ msgstr "не удалось прочитать входной файл «%s»" msgid "could not read from stdin" msgstr "не удалось прочитать из стандартного ввода" +#: transport-helper.c:1025 +#, c-format +msgid "Could not read ref %s" +msgstr "Не удалось прочитать ссылку %s" + #: unpack-trees.c:203 msgid "Checking out files" msgstr "Распаковка файлов" @@ -1527,368 +1570,423 @@ msgstr "неправильный номер порта" msgid "invalid '..' path segment" msgstr "неправильная часть пути «..»" -#: wrapper.c:523 +#: wrapper.c:219 wrapper.c:362 +#, c-format +msgid "could not open '%s' for reading and writing" +msgstr "не удалось открыть «%s» для чтения и записи" + +#: wrapper.c:221 wrapper.c:364 +#, c-format +msgid "could not open '%s' for writing" +msgstr "не удалось открыть «%s» для записи" + +#: wrapper.c:223 wrapper.c:366 builtin/am.c:337 builtin/commit.c:1688 +#: builtin/merge.c:1076 builtin/pull.c:380 +#, c-format +msgid "could not open '%s' for reading" +msgstr "не удалось открыть «%s» для чтения" + +#: wrapper.c:579 #, c-format msgid "unable to access '%s': %s" msgstr "«%s» недоступно: %s" -#: wrapper.c:544 +#: wrapper.c:600 #, c-format msgid "unable to access '%s'" msgstr "«%s» недоступно" -#: wrapper.c:555 +#: wrapper.c:611 #, c-format msgid "unable to look up current user in the passwd file: %s" msgstr "не удалось запросить текущего пользователя в файле passwd: %s" -#: wrapper.c:556 +#: wrapper.c:612 msgid "no such user" msgstr "нет такого пользователя" -#: wrapper.c:564 +#: wrapper.c:620 msgid "unable to get current working directory" msgstr "не удалось получить текущий рабочий каталог" -#: wrapper.c:575 +#: wrapper.c:631 #, c-format msgid "could not open %s for writing" msgstr "не удалось открыть «%s» для записи" -#: wrapper.c:587 +#: wrapper.c:642 builtin/am.c:424 #, c-format msgid "could not write to %s" msgstr "не удалось записать в %s" -#: wrapper.c:593 +#: wrapper.c:648 #, c-format msgid "could not close %s" msgstr "не удалось закрыть %s" -#: wt-status.c:150 +#: wt-status.c:149 msgid "Unmerged paths:" msgstr "Не слитые пути:" -#: wt-status.c:177 wt-status.c:204 +#: wt-status.c:176 wt-status.c:203 #, c-format msgid " (use \"git reset %s ...\" to unstage)" msgstr " (используйте «git reset %s <файл>…», чтобы убрать из индекса)" -#: wt-status.c:179 wt-status.c:206 +#: wt-status.c:178 wt-status.c:205 msgid " (use \"git rm --cached ...\" to unstage)" msgstr " (используйте «git rm --cached <файл>…», чтобы убрать из индекса)" -#: wt-status.c:183 +#: wt-status.c:182 msgid " (use \"git add ...\" to mark resolution)" msgstr " (используйте «git add <файл>…», чтобы пометить разрешение конфликта)" -#: wt-status.c:185 wt-status.c:189 +#: wt-status.c:184 wt-status.c:188 msgid " (use \"git add/rm ...\" as appropriate to mark resolution)" msgstr " (используйте «git add/rm <файл>…», чтобы пометить выбранное разрешение конфликта)" -#: wt-status.c:187 +#: wt-status.c:186 msgid " (use \"git rm ...\" to mark resolution)" msgstr " (используйте «git rm <файл>…», чтобы пометить разрешение конфликта)" -#: wt-status.c:198 wt-status.c:881 +#: wt-status.c:197 wt-status.c:880 msgid "Changes to be committed:" msgstr "Изменения, которые будут включены в коммит:" -#: wt-status.c:216 wt-status.c:890 +#: wt-status.c:215 wt-status.c:889 msgid "Changes not staged for commit:" msgstr "Изменения, которые не в индексе для коммита:" -#: wt-status.c:220 +#: wt-status.c:219 msgid " (use \"git add ...\" to update what will be committed)" msgstr " (используйте «git add <файл>…», чтобы добавить файл в индекс)" -#: wt-status.c:222 +#: wt-status.c:221 msgid " (use \"git add/rm ...\" to update what will be committed)" msgstr " (используйте «git add/rm <файл>…», чтобы добавить или удалить файл из индекса)" -#: wt-status.c:223 +#: wt-status.c:222 msgid "" " (use \"git checkout -- ...\" to discard changes in working " "directory)" msgstr " (используйте «git checkout -- <файл>…», чтобы отменить изменения\n в рабочем каталоге)" -#: wt-status.c:225 +#: wt-status.c:224 msgid " (commit or discard the untracked or modified content in submodules)" msgstr " (сделайте коммит или отмените изменения в неотслеживаемом или измененном содержимом в подмодулях)" -#: wt-status.c:237 +#: wt-status.c:236 #, c-format msgid " (use \"git %s ...\" to include in what will be committed)" msgstr " (используйте «git %s <файл>…», чтобы добавить в то, что будет включено в коммит)" -#: wt-status.c:252 +#: wt-status.c:251 msgid "both deleted:" msgstr "оба удалены:" -#: wt-status.c:254 +#: wt-status.c:253 msgid "added by us:" msgstr "добавлено нами:" -#: wt-status.c:256 +#: wt-status.c:255 msgid "deleted by them:" msgstr "удалено ими:" -#: wt-status.c:258 +#: wt-status.c:257 msgid "added by them:" msgstr "добавлено ими:" -#: wt-status.c:260 +#: wt-status.c:259 msgid "deleted by us:" msgstr "удалено нами:" -#: wt-status.c:262 +#: wt-status.c:261 msgid "both added:" msgstr "оба добавлены:" -#: wt-status.c:264 +#: wt-status.c:263 msgid "both modified:" msgstr "оба измены:" -#: wt-status.c:266 +#: wt-status.c:265 #, c-format msgid "bug: unhandled unmerged status %x" msgstr "ошибка: необработанный статус не слитых изменений %x" -#: wt-status.c:274 +#: wt-status.c:273 msgid "new file:" msgstr "новый файл:" -#: wt-status.c:276 +#: wt-status.c:275 msgid "copied:" msgstr "скопировано:" -#: wt-status.c:278 +#: wt-status.c:277 msgid "deleted:" msgstr "удалено:" -#: wt-status.c:280 +#: wt-status.c:279 msgid "modified:" msgstr "изменено:" -#: wt-status.c:282 +#: wt-status.c:281 msgid "renamed:" msgstr "переименовано:" -#: wt-status.c:284 +#: wt-status.c:283 msgid "typechange:" msgstr "изменен тип:" -#: wt-status.c:286 +#: wt-status.c:285 msgid "unknown:" msgstr "неизвестно:" -#: wt-status.c:288 +#: wt-status.c:287 msgid "unmerged:" msgstr "не слитые:" -#: wt-status.c:370 +#: wt-status.c:369 msgid "new commits, " msgstr "новые коммиты, " -#: wt-status.c:372 +#: wt-status.c:371 msgid "modified content, " msgstr "изменено содержимое, " -#: wt-status.c:374 +#: wt-status.c:373 msgid "untracked content, " msgstr "неотслеживаемое содержимое, " -#: wt-status.c:391 +#: wt-status.c:390 #, c-format msgid "bug: unhandled diff status %c" msgstr "ошибка: необработанный статус изменений %c" -#: wt-status.c:755 +#: wt-status.c:754 msgid "Submodules changed but not updated:" msgstr "Измененные, но не обновленные подмодули:" -#: wt-status.c:757 +#: wt-status.c:756 msgid "Submodule changes to be committed:" msgstr "Изменения в подмодулях, которые будут закоммичены:" -#: wt-status.c:838 +#: wt-status.c:837 msgid "" "Do not touch the line above.\n" "Everything below will be removed." msgstr "Не трогайте строку выше этой.\nВсё, что ниже — будет удалено." -#: wt-status.c:949 +#: wt-status.c:948 msgid "You have unmerged paths." msgstr "У вас есть не слитые пути." -#: wt-status.c:952 +#: wt-status.c:951 msgid " (fix conflicts and run \"git commit\")" msgstr " (разрешите конфликты, затем запустите «git commit»)" -#: wt-status.c:955 +#: wt-status.c:954 msgid "All conflicts fixed but you are still merging." msgstr "Все конфликты исправлены, но вы все еще в процессе слияния." -#: wt-status.c:958 +#: wt-status.c:957 msgid " (use \"git commit\" to conclude merge)" msgstr " (используйте «git commit», чтобы завершить слияние)" -#: wt-status.c:968 +#: wt-status.c:967 msgid "You are in the middle of an am session." msgstr "Вы в процессе сессии am." -#: wt-status.c:971 +#: wt-status.c:970 msgid "The current patch is empty." msgstr "Текущий патч пустой." -#: wt-status.c:975 +#: wt-status.c:974 msgid " (fix conflicts and then run \"git am --continue\")" msgstr " (разрешите конфликты, затем запустите «git am --continue»)" -#: wt-status.c:977 +#: wt-status.c:976 msgid " (use \"git am --skip\" to skip this patch)" msgstr " (используйте «git am --skip», чтобы пропустить этот патч)" -#: wt-status.c:979 +#: wt-status.c:978 msgid " (use \"git am --abort\" to restore the original branch)" msgstr " (используйте «git am --abort», чтобы восстановить оригинальную ветку)" -#: wt-status.c:1039 wt-status.c:1056 +#: wt-status.c:1105 +msgid "No commands done." +msgstr "Команды не выполнены." + +#: wt-status.c:1108 +#, c-format +msgid "Last command done (%d command done):" +msgid_plural "Last commands done (%d commands done):" +msgstr[0] "Последняя команда выполнена (%d команд выполнено):" +msgstr[1] "Последняя команда выполнена (%d команд выполнено):" +msgstr[2] "Последняя команда выполнена (%d команд выполнено):" +msgstr[3] "Последняя команда выполнена (%d команд выполнено):" + +#: wt-status.c:1119 +#, c-format +msgid " (see more in file %s)" +msgstr " (смотрите дополнительно в файле %s)" + +#: wt-status.c:1124 +msgid "No commands remaining." +msgstr "Команд больше не осталось." + +#: wt-status.c:1127 +#, c-format +msgid "Next command to do (%d remaining command):" +msgid_plural "Next commands to do (%d remaining commands):" +msgstr[0] "Следующая команда для выполнения (%d команда осталась):" +msgstr[1] "Следующая команда для выполнения (%d команды осталось):" +msgstr[2] "Следующая команда для выполнения (%d команд осталось):" +msgstr[3] "Следующая команда для выполнения (%d команд осталось):" + +#: wt-status.c:1135 +msgid " (use \"git rebase --edit-todo\" to view and edit)" +msgstr " (используйте «git rebase --edit-todo», чтобы просмотреть и изменить)" + +#: wt-status.c:1148 #, c-format msgid "You are currently rebasing branch '%s' on '%s'." msgstr "Вы сейчас перемещаете ветку «%s» над «%s»." -#: wt-status.c:1044 wt-status.c:1061 +#: wt-status.c:1153 msgid "You are currently rebasing." msgstr "Вы сейчас перемещаете ветку." -#: wt-status.c:1047 +#: wt-status.c:1167 msgid " (fix conflicts and then run \"git rebase --continue\")" msgstr " (разрешите конфликты, затем запустите «git rebase --continue»)" -#: wt-status.c:1049 +#: wt-status.c:1169 msgid " (use \"git rebase --skip\" to skip this patch)" msgstr " (используйте «git rebase --skip», чтобы пропустить этот патч)" -#: wt-status.c:1051 +#: wt-status.c:1171 msgid " (use \"git rebase --abort\" to check out the original branch)" msgstr " (используйте «git rebase --abort», чтобы перейти на оригинальную ветку)" -#: wt-status.c:1064 +#: wt-status.c:1177 msgid " (all conflicts fixed: run \"git rebase --continue\")" msgstr " (все конфликты разрешены: запустите «git rebase --continue»)" -#: wt-status.c:1068 +#: wt-status.c:1181 #, c-format msgid "" "You are currently splitting a commit while rebasing branch '%s' on '%s'." msgstr "Вы сейчас разделяете коммит при перемещении ветки «%s» над «%s»." -#: wt-status.c:1073 +#: wt-status.c:1186 msgid "You are currently splitting a commit during a rebase." msgstr "Вы сейчас разделяете коммит при перемещении ветки." -#: wt-status.c:1076 +#: wt-status.c:1189 msgid " (Once your working directory is clean, run \"git rebase --continue\")" msgstr "(Как только ваш рабочий каталог будет чистый, запустите «git rebase --continue»)" -#: wt-status.c:1080 +#: wt-status.c:1193 #, c-format msgid "You are currently editing a commit while rebasing branch '%s' on '%s'." msgstr "Вы сейчас редактируете коммит при перемещении ветки «%s» над «%s»." -#: wt-status.c:1085 +#: wt-status.c:1198 msgid "You are currently editing a commit during a rebase." msgstr "Вы сейчас редактируете коммит при перемещении ветки." -#: wt-status.c:1088 +#: wt-status.c:1201 msgid " (use \"git commit --amend\" to amend the current commit)" msgstr " (используйте «git commit --amend», чтобы исправить текущий коммит)" -#: wt-status.c:1090 +#: wt-status.c:1203 msgid " (use \"git rebase --continue\" once you are satisfied with your changes)" msgstr " (используйте «git rebase --continue», когда будете довольны изменениями)" -#: wt-status.c:1100 +#: wt-status.c:1213 #, c-format msgid "You are currently cherry-picking commit %s." msgstr "Вы сейчас отбираете лучший коммит %s." -#: wt-status.c:1105 +#: wt-status.c:1218 msgid " (fix conflicts and run \"git cherry-pick --continue\")" msgstr " (разрешите конфликты, затем запустите «git cherry-pick --continue»)" -#: wt-status.c:1108 +#: wt-status.c:1221 msgid " (all conflicts fixed: run \"git cherry-pick --continue\")" msgstr " (все конфликты разрешены: запустите «git cherry-pick --continue»)" -#: wt-status.c:1110 +#: wt-status.c:1223 msgid " (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)" msgstr " (используйте «git cherry-pick --abort», чтобы отменить операцию отбора лучшего)" -#: wt-status.c:1119 +#: wt-status.c:1232 #, c-format msgid "You are currently reverting commit %s." msgstr "Вы сейчас возвращаете коммит %s." -#: wt-status.c:1124 +#: wt-status.c:1237 msgid " (fix conflicts and run \"git revert --continue\")" msgstr " (разрешите конфликты, затем запустите «git revert --continue»)" -#: wt-status.c:1127 +#: wt-status.c:1240 msgid " (all conflicts fixed: run \"git revert --continue\")" msgstr " (все конфликты разрешены: запустите «git revert --continue»)" -#: wt-status.c:1129 +#: wt-status.c:1242 msgid " (use \"git revert --abort\" to cancel the revert operation)" msgstr " (используйте «git revert --abort», чтобы отменить операцию возврата)" -#: wt-status.c:1140 +#: wt-status.c:1253 #, c-format msgid "You are currently bisecting, started from branch '%s'." msgstr "Вы сейчас в процессе двоичного поиска, начатого с ветки «%s»." -#: wt-status.c:1144 +#: wt-status.c:1257 msgid "You are currently bisecting." msgstr "Вы сейчас в процессе двоичного поиска." -#: wt-status.c:1147 +#: wt-status.c:1260 msgid " (use \"git bisect reset\" to get back to the original branch)" msgstr " (используйте «git bisect reset», чтобы вернуться на исходную ветку)" -#: wt-status.c:1324 +#: wt-status.c:1437 msgid "On branch " msgstr "На ветке " -#: wt-status.c:1331 +#: wt-status.c:1445 +msgid "interactive rebase in progress; onto " +msgstr "интерактивное перемещение в процессе; над " + +#: wt-status.c:1447 msgid "rebase in progress; onto " msgstr "перемещение в процессе; над " -#: wt-status.c:1336 +#: wt-status.c:1452 msgid "HEAD detached at " msgstr "HEAD отделен на " -#: wt-status.c:1338 +#: wt-status.c:1454 msgid "HEAD detached from " msgstr "HEAD отделен начиная с " -#: wt-status.c:1341 +#: wt-status.c:1457 msgid "Not currently on any branch." msgstr "Сейчас ни на одной из веток" -#: wt-status.c:1358 +#: wt-status.c:1474 msgid "Initial commit" msgstr "Начальный коммит" -#: wt-status.c:1372 +#: wt-status.c:1488 msgid "Untracked files" msgstr "Неотслеживаемые файлы" -#: wt-status.c:1374 +#: wt-status.c:1490 msgid "Ignored files" msgstr "Игнорируемые файлы" -#: wt-status.c:1378 +#: wt-status.c:1494 #, c-format msgid "" "It took %.2f seconds to enumerate untracked files. 'status -uno'\n" @@ -1896,78 +1994,78 @@ msgid "" "new files yourself (see 'git help status')." msgstr "%.2f секунды занял вывод списка неотслеживаемых файлов. «status -uno» возможно может ускорить это, но будьте внимательны, и не забудьте добавить новые файлы вручную (смотрите «git help status» для подробностей)." -#: wt-status.c:1384 +#: wt-status.c:1500 #, c-format msgid "Untracked files not listed%s" msgstr "Неотслеживаемые файлы не показаны%s" -#: wt-status.c:1386 +#: wt-status.c:1502 msgid " (use -u option to show untracked files)" msgstr "(используйте опцию «-u», чтобы показать неотслеживаемые файлы)" -#: wt-status.c:1392 +#: wt-status.c:1508 msgid "No changes" msgstr "Нет изменений" -#: wt-status.c:1397 +#: wt-status.c:1513 #, c-format msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n" msgstr "нет изменений добавленных для коммита\n(используйте «git add» и/или «git commit -a»)\n" -#: wt-status.c:1400 +#: wt-status.c:1516 #, c-format msgid "no changes added to commit\n" msgstr "нет изменений добавленных для коммита\n" -#: wt-status.c:1403 +#: wt-status.c:1519 #, c-format msgid "" "nothing added to commit but untracked files present (use \"git add\" to " "track)\n" msgstr "ничего не добавлено в коммит, но есть неотслеживаемые файлы (используйте \"git add\", чтобы отслеживать их)\n" -#: wt-status.c:1406 +#: wt-status.c:1522 #, c-format msgid "nothing added to commit but untracked files present\n" msgstr "ничего не добавлено в коммит, но есть неотслеживаемые файлы\n" -#: wt-status.c:1409 +#: wt-status.c:1525 #, c-format msgid "nothing to commit (create/copy files and use \"git add\" to track)\n" msgstr "нечего коммитить (создайте/скопируйте файлы, затем запустите «git add», чтобы отслеживать их)\n" -#: wt-status.c:1412 wt-status.c:1417 +#: wt-status.c:1528 wt-status.c:1533 #, c-format msgid "nothing to commit\n" msgstr "нечего коммитить\n" -#: wt-status.c:1415 +#: wt-status.c:1531 #, c-format msgid "nothing to commit (use -u to show untracked files)\n" msgstr "нечего коммитить (используйте опцию «-u», чтобы показать неотслеживаемые файлы)\n" -#: wt-status.c:1419 +#: wt-status.c:1535 #, c-format msgid "nothing to commit, working directory clean\n" msgstr "нечего коммитить, нет изменений в рабочем каталоге\n" -#: wt-status.c:1528 +#: wt-status.c:1644 msgid "HEAD (no branch)" msgstr "HEAD (нет ветки)" -#: wt-status.c:1534 +#: wt-status.c:1650 msgid "Initial commit on " msgstr "Начальный коммит на " -#: wt-status.c:1561 +#: wt-status.c:1677 msgid "gone" msgstr "исчез" -#: wt-status.c:1563 wt-status.c:1571 +#: wt-status.c:1679 wt-status.c:1687 msgid "behind " msgstr "позади" -#: compat/precompose_utf8.c:55 builtin/clone.c:345 +#: compat/precompose_utf8.c:55 builtin/clone.c:403 #, c-format msgid "failed to unlink '%s'" msgstr "сбой отсоединения «%s»" @@ -1994,7 +2092,7 @@ msgstr "удалить «%s»\n" msgid "Unstaged changes after refreshing the index:" msgstr "Непроиндексированные изменения после обновления индекса:" -#: builtin/add.c:194 builtin/rev-parse.c:796 +#: builtin/add.c:194 builtin/rev-parse.c:799 msgid "Could not read the index" msgstr "Не удалось прочитать индекс" @@ -2029,15 +2127,15 @@ msgstr "Не удалось применить «%s»" msgid "The following paths are ignored by one of your .gitignore files:\n" msgstr "Следующие пути игнорируются одним из ваших файлов .gitignore:\n" -#: builtin/add.c:249 builtin/clean.c:874 builtin/fetch.c:107 builtin/mv.c:110 -#: builtin/prune-packed.c:55 builtin/push.c:508 builtin/remote.c:1369 -#: builtin/rm.c:268 +#: builtin/add.c:249 builtin/clean.c:896 builtin/fetch.c:108 builtin/mv.c:110 +#: builtin/prune-packed.c:55 builtin/pull.c:182 builtin/push.c:545 +#: builtin/remote.c:1339 builtin/rm.c:268 builtin/send-pack.c:162 msgid "dry run" msgstr "пробный запуск" #: builtin/add.c:250 builtin/apply.c:4580 builtin/check-ignore.c:19 -#: builtin/commit.c:1322 builtin/count-objects.c:63 builtin/fsck.c:616 -#: builtin/log.c:1617 builtin/mv.c:109 builtin/read-tree.c:114 +#: builtin/commit.c:1321 builtin/count-objects.c:63 builtin/fsck.c:636 +#: builtin/log.c:1641 builtin/mv.c:109 builtin/read-tree.c:114 msgid "be verbose" msgstr "быть многословнее" @@ -2045,7 +2143,7 @@ msgstr "быть многословнее" msgid "interactive picking" msgstr "интерактивный выбор" -#: builtin/add.c:253 builtin/checkout.c:1221 builtin/reset.c:286 +#: builtin/add.c:253 builtin/checkout.c:1152 builtin/reset.c:286 msgid "select hunks interactively" msgstr "интерактивный выбор блоков" @@ -2112,15 +2210,403 @@ msgstr "Ничего не указано, ничего не добавлено.\ msgid "Maybe you wanted to say 'git add .'?\n" msgstr "Возможно, вы имели в виду «git add .»?\n" -#: builtin/add.c:364 builtin/check-ignore.c:172 builtin/clean.c:918 -#: builtin/commit.c:335 builtin/mv.c:130 builtin/reset.c:235 builtin/rm.c:298 +#: builtin/add.c:364 builtin/check-ignore.c:172 builtin/clean.c:940 +#: builtin/commit.c:336 builtin/mv.c:130 builtin/reset.c:235 builtin/rm.c:298 msgid "index file corrupt" msgstr "файл индекса поврежден" -#: builtin/add.c:447 builtin/apply.c:4678 builtin/mv.c:279 builtin/rm.c:430 +#: builtin/add.c:445 builtin/apply.c:4678 builtin/mv.c:279 builtin/rm.c:430 msgid "Unable to write new index file" msgstr "Не удалось записать новый файл индекса" +#: builtin/am.c:41 +#, c-format +msgid "could not stat %s" +msgstr "не удалось выполнить stat для %s" + +#: builtin/am.c:270 builtin/am.c:1345 builtin/commit.c:737 +#: builtin/merge.c:1079 +#, c-format +msgid "could not read '%s'" +msgstr "не удалось прочитать «%s»" + +#: builtin/am.c:444 +msgid "could not parse author script" +msgstr "не удалось разобрать сценарий авторства" + +#: builtin/am.c:521 +#, c-format +msgid "'%s' was deleted by the applypatch-msg hook" +msgstr "«%s» был удален перехватчиком applypatch-msg" + +#: builtin/am.c:562 builtin/notes.c:300 +#, c-format +msgid "Malformed input line: '%s'." +msgstr "Плохая строка ввода: «%s»." + +#: builtin/am.c:599 builtin/notes.c:315 +#, c-format +msgid "Failed to copy notes from '%s' to '%s'" +msgstr "Не удалось скопировать заметку из «%s» в «%s»" + +#: builtin/am.c:625 +msgid "fseek failed" +msgstr "сбой при выполнении fseek" + +#: builtin/am.c:786 builtin/am.c:874 +#, c-format +msgid "could not open '%s' for reading: %s" +msgstr "не удалось открыть «%s» для чтения: %s" + +#: builtin/am.c:793 +#, c-format +msgid "could not open '%s' for writing: %s" +msgstr "не удалось открыть «%s» для записи: %s" + +#: builtin/am.c:802 +#, c-format +msgid "could not parse patch '%s'" +msgstr "не удалось разобрать патч «%s»" + +#: builtin/am.c:867 +msgid "Only one StGIT patch series can be applied at once" +msgstr "Только серия патчей StGIT может быть применена за раз" + +#: builtin/am.c:915 +msgid "invalid timestamp" +msgstr "недопустимая метка даты/времени" + +#: builtin/am.c:918 builtin/am.c:926 +msgid "invalid Date line" +msgstr "недопустимая строка даты" + +#: builtin/am.c:923 +msgid "invalid timezone offset" +msgstr "недопустимое смещение часового пояса" + +#: builtin/am.c:1010 +msgid "Patch format detection failed." +msgstr "Сбой определения формата патча." + +#: builtin/am.c:1015 builtin/clone.c:368 +#, c-format +msgid "failed to create directory '%s'" +msgstr "не удалось создать каталог «%s»" + +#: builtin/am.c:1019 +msgid "Failed to split patches." +msgstr "Не удалось разделить патчи на части." + +#: builtin/am.c:1151 builtin/commit.c:362 +msgid "unable to write index file" +msgstr "не удалось записать индекс" + +#: builtin/am.c:1202 +#, c-format +msgid "When you have resolved this problem, run \"%s --continue\"." +msgstr "Когда вы устраните эту проблему, запустите «%s --continue»." + +#: builtin/am.c:1203 +#, c-format +msgid "If you prefer to skip this patch, run \"%s --skip\" instead." +msgstr "Если вы хотите пропустить этот патч, то запустите «%s --skip»." + +#: builtin/am.c:1204 +#, c-format +msgid "To restore the original branch and stop patching, run \"%s --abort\"." +msgstr "Чтобы вернуться на предыдущую ветку и остановить применение изменений, запустите «%s --abort»." + +#: builtin/am.c:1339 +msgid "Patch is empty. Was it split wrong?" +msgstr "Патч пуст. Возможно, он был неправильно разделён?" + +#: builtin/am.c:1413 builtin/log.c:1345 +#, c-format +msgid "invalid ident line: %s" +msgstr "неправильная строка идентификации: %s" + +#: builtin/am.c:1440 +#, c-format +msgid "unable to parse commit %s" +msgstr "не удалось разобрать коммит %s" + +#: builtin/am.c:1614 +msgid "Repository lacks necessary blobs to fall back on 3-way merge." +msgstr "В репозитории отсутствуют двоичные объекты, необходимые для отката к трехходовому слиянию." + +#: builtin/am.c:1616 +msgid "Using index info to reconstruct a base tree..." +msgstr "Использую индекс для реконструкции базового дерева…" + +#: builtin/am.c:1635 +msgid "" +"Did you hand edit your patch?\n" +"It does not apply to blobs recorded in its index." +msgstr "Вы вручную изменяли патч?\nОн не накладывается без ошибок на двоичные объекты, записанные в его заголовке." + +#: builtin/am.c:1641 +msgid "Falling back to patching base and 3-way merge..." +msgstr "Откат к применению изменений к базовому коммиту с помощью трехходового слияния…" + +#: builtin/am.c:1666 +msgid "Failed to merge in the changes." +msgstr "Не удалось слить изменения." + +#: builtin/am.c:1691 builtin/merge.c:632 +msgid "git write-tree failed to write a tree" +msgstr "git write-tree не удалось записать дерево" + +#: builtin/am.c:1698 +msgid "applying to an empty history" +msgstr "применение к пустой истории" + +#: builtin/am.c:1711 builtin/commit.c:1752 builtin/merge.c:829 +#: builtin/merge.c:854 +msgid "failed to write commit object" +msgstr "сбой записи объекта коммита" + +#: builtin/am.c:1743 builtin/am.c:1747 +#, c-format +msgid "cannot resume: %s does not exist." +msgstr "нельзя продолжнить: %s не существует " + +#: builtin/am.c:1763 +msgid "cannot be interactive without stdin connected to a terminal." +msgstr "не удалось использовать интерактивное поведение, без stdin подключенного к терминалу." + +#: builtin/am.c:1768 +msgid "Commit Body is:" +msgstr "Тело коммита:" + +#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] +#. in your translation. The program will only accept English +#. input at this point. +#: builtin/am.c:1778 +msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: " +msgstr "Применить? [y] - да/[n] - нет/[e] - редактировать/[v] - просмотреть патч/[a] - применить всё: " + +#: builtin/am.c:1828 +#, c-format +msgid "Dirty index: cannot apply patches (dirty: %s)" +msgstr "Индекс не пустой: нельзя применять патчи (в индексе: %s)" + +#: builtin/am.c:1863 builtin/am.c:1934 +#, c-format +msgid "Applying: %.*s" +msgstr "Применение: %.*s" + +#: builtin/am.c:1879 +msgid "No changes -- Patch already applied." +msgstr "Нет изменений — Патч уже применен." + +#: builtin/am.c:1887 +#, c-format +msgid "Patch failed at %s %.*s" +msgstr "Ошибка применения изменений на %s %.*s" + +#: builtin/am.c:1893 +#, c-format +msgid "The copy of the patch that failed is found in: %s" +msgstr "Копию изменений, которые не удалось применить, вы можете найти в: %s" + +#: builtin/am.c:1937 +msgid "" +"No changes - did you forget to use 'git add'?\n" +"If there is nothing left to stage, chances are that something else\n" +"already introduced the same changes; you might want to skip this patch." +msgstr "Нет изменений — возможно, вы забыли вызвать «git add»?\nЕсли ничего не осталось для индексации, то, скорее всего, что-то другое уже сделало те же изменения; возможно, вам следует пропустить этот патч." + +#: builtin/am.c:1944 +msgid "" +"You still have unmerged paths in your index.\n" +"Did you forget to use 'git add'?" +msgstr "У вас все еще имеются не слитые пути в индексе.\nВозможно, вы забыли вызвать «git add»?" + +#: builtin/am.c:2052 builtin/am.c:2056 builtin/am.c:2068 builtin/reset.c:308 +#: builtin/reset.c:316 +#, c-format +msgid "Could not parse object '%s'." +msgstr "Не удалось разобрать объект «%s»." + +#: builtin/am.c:2104 +msgid "failed to clean index" +msgstr "не удалось очистить индекс" + +#: builtin/am.c:2138 +msgid "" +"You seem to have moved HEAD since the last 'am' failure.\n" +"Not rewinding to ORIG_HEAD" +msgstr "Похоже, что вы переместили HEAD с момента последней ошибки выполнения «am».\nПеремотка на ORIG_HEAD не выполняется" + +#: builtin/am.c:2199 +#, c-format +msgid "Invalid value for --patch-format: %s" +msgstr "Неправильное значение для --patch-format: %s" + +#: builtin/am.c:2221 +msgid "git am [options] [(|)...]" +msgstr "git am [опции] [(|)…]" + +#: builtin/am.c:2222 +msgid "git am [options] (--continue | --skip | --abort)" +msgstr "git am [опции] (--continue | --skip | --abort)" + +#: builtin/am.c:2228 +msgid "run interactively" +msgstr "запустить в интерактивном режиме" + +#: builtin/am.c:2230 +msgid "historical option -- no-op" +msgstr "историческая опция — ничего не делает" + +#: builtin/am.c:2232 +msgid "allow fall back on 3way merging if needed" +msgstr "разрешить откатиться к трехходовому слиянию, если нужно" + +#: builtin/am.c:2233 builtin/init-db.c:509 builtin/prune-packed.c:57 +#: builtin/repack.c:171 +msgid "be quiet" +msgstr "тихий режим" + +#: builtin/am.c:2235 +msgid "add a Signed-off-by line to the commit message" +msgstr "добавить строку Signed-off-by к сообщению коммита" + +#: builtin/am.c:2238 +msgid "recode into utf8 (default)" +msgstr "перекодировать в utf8 (по умолчанию)" + +#: builtin/am.c:2240 +msgid "pass -k flag to git-mailinfo" +msgstr "передать флаг -k в git-mailinfo" + +#: builtin/am.c:2242 +msgid "pass -b flag to git-mailinfo" +msgstr "передать флаг -b в git-mailinfo" + +#: builtin/am.c:2244 +msgid "pass -m flag to git-mailinfo" +msgstr "передать флаг -m в git-mailinfo" + +#: builtin/am.c:2246 +msgid "pass --keep-cr flag to git-mailsplit for mbox format" +msgstr "передать флаг --keep-cr в git-mailsplit для формата mbox" + +#: builtin/am.c:2249 +msgid "do not pass --keep-cr flag to git-mailsplit independent of am.keepcr" +msgstr "не передавать --keep-cr флаг в git-mailsplit вне зависимости от am.keepcr" + +#: builtin/am.c:2252 +msgid "strip everything before a scissors line" +msgstr "обрезать все до строки обрезки" + +#: builtin/am.c:2253 builtin/apply.c:4563 +msgid "action" +msgstr "действие" + +#: builtin/am.c:2254 builtin/am.c:2257 builtin/am.c:2260 builtin/am.c:2263 +#: builtin/am.c:2266 builtin/am.c:2269 builtin/am.c:2272 builtin/am.c:2275 +#: builtin/am.c:2281 +msgid "pass it through git-apply" +msgstr "передать его в git-apply" + +#: builtin/am.c:2262 builtin/apply.c:4587 +msgid "root" +msgstr "корень" + +#: builtin/am.c:2265 builtin/am.c:2268 builtin/apply.c:4525 +#: builtin/apply.c:4528 builtin/clone.c:85 builtin/fetch.c:93 +#: builtin/pull.c:167 +msgid "path" +msgstr "путь" + +#: builtin/am.c:2271 builtin/fmt-merge-msg.c:669 builtin/fmt-merge-msg.c:672 +#: builtin/grep.c:698 builtin/merge.c:198 builtin/pull.c:127 +#: builtin/repack.c:178 builtin/repack.c:182 builtin/show-branch.c:664 +#: builtin/show-ref.c:180 builtin/tag.c:591 parse-options.h:132 +#: parse-options.h:134 parse-options.h:243 +msgid "n" +msgstr "n" + +#: builtin/am.c:2274 builtin/apply.c:4531 +msgid "num" +msgstr "количество" + +#: builtin/am.c:2277 builtin/for-each-ref.c:34 builtin/replace.c:438 +msgid "format" +msgstr "формат" + +#: builtin/am.c:2278 +msgid "format the patch(es) are in" +msgstr "формат, в котором находятся патчи" + +#: builtin/am.c:2284 +msgid "override error message when patch failure occurs" +msgstr "переопределить сообщение об ошибке, если не удалось наложить изменения" + +#: builtin/am.c:2286 +msgid "continue applying patches after resolving a conflict" +msgstr "продолжить применение изменений после разрешения конфиликта" + +#: builtin/am.c:2289 +msgid "synonyms for --continue" +msgstr "синонимы для --continue" + +#: builtin/am.c:2292 +msgid "skip the current patch" +msgstr "пропустить текущий патч" + +#: builtin/am.c:2295 +msgid "restore the original branch and abort the patching operation." +msgstr "восстановить оригинальную ветку и отменить операцию применения изменений." + +#: builtin/am.c:2299 +msgid "lie about committer date" +msgstr "соврать о дате коммитера" + +#: builtin/am.c:2301 +msgid "use current timestamp for author date" +msgstr "использовать текущее время как время авторства" + +#: builtin/am.c:2303 builtin/commit.c:1590 builtin/merge.c:225 +#: builtin/pull.c:155 builtin/revert.c:92 builtin/tag.c:606 +msgid "key-id" +msgstr "key-id" + +#: builtin/am.c:2304 +msgid "GPG-sign commits" +msgstr "подписать коммиты с помощью GPG" + +#: builtin/am.c:2307 +msgid "(internal use for git-rebase)" +msgstr "(внутреннее использование для git-rebase)" + +#: builtin/am.c:2322 +msgid "" +"The -b/--binary option has been a no-op for long time, and\n" +"it will be removed. Please do not use it anymore." +msgstr "Опция -b/--binary уже долгое время ничего не делает и будет удалена с следующих версиях Git. Пожалуйста, не используйте ее." + +#: builtin/am.c:2329 +msgid "failed to read the index" +msgstr "сбой чтения индекса" + +#: builtin/am.c:2344 +#, c-format +msgid "previous rebase directory %s still exists but mbox given." +msgstr "предыдущий каталог перемещения %s еще существует, но передан mbox." + +#: builtin/am.c:2368 +#, c-format +msgid "" +"Stray %s directory found.\n" +"Use \"git am --abort\" to remove it." +msgstr "Найден забытый каталог %s.\nИспользуйте «git am --abort», чтобы удалить его." + +#: builtin/am.c:2374 +msgid "Resolve operation not in progress, we are not resuming." +msgstr "Операция разрешения конфликтов не в процессе выполнения, не продолжаем." + #: builtin/apply.c:59 msgid "git apply [] [...]" msgstr "git apply [<опции>] [<патч>…]" @@ -2381,7 +2867,7 @@ msgstr "%s: не удалось применить патч" msgid "Checking patch %s..." msgstr "Проверка патча %s…" -#: builtin/apply.c:3909 builtin/checkout.c:233 builtin/reset.c:135 +#: builtin/apply.c:3909 builtin/checkout.c:232 builtin/reset.c:135 #, c-format msgid "make_cache_entry failed for path '%s'" msgstr "сбой make_cache_entry для пути «%s»" @@ -2462,11 +2948,6 @@ msgstr "не распознанный ввод" msgid "unable to read index file" msgstr "не удалось прочитать файл индекса" -#: builtin/apply.c:4525 builtin/apply.c:4528 builtin/clone.c:85 -#: builtin/fetch.c:92 -msgid "path" -msgstr "путь" - #: builtin/apply.c:4526 msgid "don't apply changes matching the given path" msgstr "не применять изменения по указанному пути" @@ -2475,10 +2956,6 @@ msgstr "не применять изменения по указанному п msgid "apply changes matching the given path" msgstr "применять изменения по указанному пути" -#: builtin/apply.c:4531 -msgid "num" -msgstr "количество" - #: builtin/apply.c:4532 msgid "remove leading slashes from traditional diff paths" msgstr "удалить <количество> ведущих косых черт из традиционных путей списка изменений" @@ -2535,10 +3012,6 @@ msgstr "пути, отделенные НУЛЕВЫМ символом" msgid "ensure at least lines of context match" msgstr "удостовериться, что по крайней мере строк контекста совпадают" -#: builtin/apply.c:4563 -msgid "action" -msgstr "действие" - #: builtin/apply.c:4564 msgid "detect new or modified lines that have whitespace errors" msgstr "определять новые или модифицированные строки, у которых есть ошибки в пробельных символах" @@ -2571,10 +3044,6 @@ msgstr "разрешить некорректно определенные пр msgid "do not trust the line counts in the hunk headers" msgstr "не доверять количеству строк из заголовка блока изменений" -#: builtin/apply.c:4587 -msgid "root" -msgstr "корень" - #: builtin/apply.c:4588 msgid "prepend to all filenames" msgstr "добавить <корень> спереди ко всем именам файлов" @@ -2661,11 +3130,11 @@ msgstr "выполнить «git bisect next»" msgid "update BISECT_HEAD instead of checking out the current commit" msgstr "обновить BISECT_HEAD вместо перехода на текущий коммит" -#: builtin/blame.c:31 +#: builtin/blame.c:32 msgid "git blame [] [] [] [--] " msgstr "git blame [<опции>] [<опции-редакции>] [<редакция>] [--] <файл>" -#: builtin/blame.c:36 +#: builtin/blame.c:37 msgid " are documented in git-rev-list(1)" msgstr "<опции-rev-list> документированы в git-rev-list(1)" @@ -2840,323 +3309,323 @@ msgstr "внешняя отслеживаемая ветка «%s» не най msgid "branch '%s' not found." msgstr "ветка «%s» не найдена." -#: builtin/branch.c:258 +#: builtin/branch.c:259 #, c-format msgid "Error deleting remote-tracking branch '%s'" msgstr "Ошибка удаления внешней отслеживаемой ветки «%s»" -#: builtin/branch.c:259 +#: builtin/branch.c:260 #, c-format msgid "Error deleting branch '%s'" msgstr "Ошибка удаления ветки «%s»" -#: builtin/branch.c:266 +#: builtin/branch.c:267 #, c-format msgid "Deleted remote-tracking branch %s (was %s).\n" msgstr "Внешняя отслеживаемая ветка %s удалена (была %s).\n" -#: builtin/branch.c:267 +#: builtin/branch.c:268 #, c-format msgid "Deleted branch %s (was %s).\n" msgstr "Ветка %s удалена (была %s).\n" -#: builtin/branch.c:368 +#: builtin/branch.c:369 #, c-format msgid "branch '%s' does not point at a commit" msgstr "ветка «%s» не указывает на коммит" -#: builtin/branch.c:451 +#: builtin/branch.c:452 #, c-format msgid "[%s: gone]" msgstr "[%s: пропал]" -#: builtin/branch.c:456 +#: builtin/branch.c:457 #, c-format msgid "[%s]" msgstr "[%s]" -#: builtin/branch.c:461 +#: builtin/branch.c:462 #, c-format msgid "[%s: behind %d]" msgstr "[%s: позади %d]" -#: builtin/branch.c:463 +#: builtin/branch.c:464 #, c-format msgid "[behind %d]" msgstr "[позади %d]" -#: builtin/branch.c:467 +#: builtin/branch.c:468 #, c-format msgid "[%s: ahead %d]" msgstr "[%s: впереди %d]" -#: builtin/branch.c:469 +#: builtin/branch.c:470 #, c-format msgid "[ahead %d]" msgstr "[впереди %d]" -#: builtin/branch.c:472 +#: builtin/branch.c:473 #, c-format msgid "[%s: ahead %d, behind %d]" msgstr "[%s: впереди %d, позади %d]" -#: builtin/branch.c:475 +#: builtin/branch.c:476 #, c-format msgid "[ahead %d, behind %d]" msgstr "[впереди %d, позади %d]" -#: builtin/branch.c:488 +#: builtin/branch.c:489 msgid " **** invalid ref ****" msgstr " **** недействительная ссылка ****" -#: builtin/branch.c:579 +#: builtin/branch.c:580 #, c-format msgid "(no branch, rebasing %s)" msgstr "(нет ветки, перемещение %s)" -#: builtin/branch.c:582 +#: builtin/branch.c:583 #, c-format msgid "(no branch, bisect started on %s)" msgstr "(нет ветки, двоичный поиск начат на %s)" -#: builtin/branch.c:588 +#: builtin/branch.c:589 #, c-format msgid "(HEAD detached at %s)" msgstr "(HEAD отделён на %s)" -#: builtin/branch.c:591 +#: builtin/branch.c:592 #, c-format msgid "(HEAD detached from %s)" msgstr "(HEAD отделён начиная с %s)" -#: builtin/branch.c:595 +#: builtin/branch.c:596 msgid "(no branch)" msgstr "(нет ветки)" -#: builtin/branch.c:642 +#: builtin/branch.c:643 #, c-format msgid "object '%s' does not point to a commit" msgstr "объект «%s» не указывает на коммит" -#: builtin/branch.c:690 +#: builtin/branch.c:691 msgid "some refs could not be read" msgstr "не удается прочитать некоторые ссылки" -#: builtin/branch.c:703 +#: builtin/branch.c:704 msgid "cannot rename the current branch while not on any." msgstr "невозможно переименовать текущую ветку, если вы не находитесь ни на одной из них." -#: builtin/branch.c:713 +#: builtin/branch.c:714 #, c-format msgid "Invalid branch name: '%s'" msgstr "Недействительное имя ветки: «%s»" -#: builtin/branch.c:728 +#: builtin/branch.c:729 msgid "Branch rename failed" msgstr "Сбой переименования ветки" -#: builtin/branch.c:732 +#: builtin/branch.c:733 #, c-format msgid "Renamed a misnamed branch '%s' away" msgstr "Переименована неправильно названная ветка «%s»" -#: builtin/branch.c:736 +#: builtin/branch.c:737 #, c-format msgid "Branch renamed to %s, but HEAD is not updated!" msgstr "Ветка переименована в %s, но HEAD не обновлен!" -#: builtin/branch.c:743 +#: builtin/branch.c:744 msgid "Branch is renamed, but update of config-file failed" msgstr "Ветка переименована, но произошел сбой обновления файла конфигурации" -#: builtin/branch.c:758 +#: builtin/branch.c:759 #, c-format msgid "malformed object name %s" msgstr "плохое имя объекта %s" -#: builtin/branch.c:780 +#: builtin/branch.c:781 #, c-format msgid "could not write branch description template: %s" msgstr "не удалось записать шаблон описания ветки: %s" -#: builtin/branch.c:810 +#: builtin/branch.c:811 msgid "Generic options" msgstr "Общие параметры" -#: builtin/branch.c:812 +#: builtin/branch.c:813 msgid "show hash and subject, give twice for upstream branch" msgstr "показывать хеш-сумму и тему, укажите дважды для вышестоящей ветки" -#: builtin/branch.c:813 +#: builtin/branch.c:814 msgid "suppress informational messages" msgstr "не выводить информационные сообщения" -#: builtin/branch.c:814 +#: builtin/branch.c:815 msgid "set up tracking mode (see git-pull(1))" msgstr "установить режим отслеживания вышестоящей ветки (см. git-pull(1))" -#: builtin/branch.c:816 +#: builtin/branch.c:817 msgid "change upstream info" msgstr "изменить информацию о вышестоящей ветке" -#: builtin/branch.c:820 +#: builtin/branch.c:821 msgid "use colored output" msgstr "использовать цветной вывод" -#: builtin/branch.c:821 +#: builtin/branch.c:822 msgid "act on remote-tracking branches" msgstr "выполнить действия на отслеживаемых внешних ветках" -#: builtin/branch.c:824 builtin/branch.c:830 builtin/branch.c:851 -#: builtin/branch.c:857 builtin/commit.c:1581 builtin/commit.c:1582 -#: builtin/commit.c:1583 builtin/commit.c:1584 builtin/tag.c:616 -#: builtin/tag.c:622 +#: builtin/branch.c:825 builtin/branch.c:831 builtin/branch.c:852 +#: builtin/branch.c:858 builtin/commit.c:1580 builtin/commit.c:1581 +#: builtin/commit.c:1582 builtin/commit.c:1583 builtin/tag.c:618 +#: builtin/tag.c:624 msgid "commit" msgstr "коммит" -#: builtin/branch.c:825 builtin/branch.c:831 +#: builtin/branch.c:826 builtin/branch.c:832 msgid "print only branches that contain the commit" msgstr "вывод только веток, которые содержат коммит" -#: builtin/branch.c:837 +#: builtin/branch.c:838 msgid "Specific git-branch actions:" msgstr "Специфичные для git-branch действия:" -#: builtin/branch.c:838 +#: builtin/branch.c:839 msgid "list both remote-tracking and local branches" msgstr "показать список и отслеживаемых и локальных веток" -#: builtin/branch.c:840 +#: builtin/branch.c:841 msgid "delete fully merged branch" msgstr "удалить полностью слитую ветку" -#: builtin/branch.c:841 +#: builtin/branch.c:842 msgid "delete branch (even if not merged)" msgstr "удалить ветку (даже никуда не слитую)" -#: builtin/branch.c:842 +#: builtin/branch.c:843 msgid "move/rename a branch and its reflog" msgstr "переместить/переименовать ветки и ее журнал ссылок" -#: builtin/branch.c:843 +#: builtin/branch.c:844 msgid "move/rename a branch, even if target exists" msgstr "переместить/переименовать ветку, даже если целевое имя уже существует" -#: builtin/branch.c:844 +#: builtin/branch.c:845 msgid "list branch names" msgstr "показать список имен веток" -#: builtin/branch.c:845 +#: builtin/branch.c:846 msgid "create the branch's reflog" msgstr "создать журнал ссылок ветки" -#: builtin/branch.c:847 +#: builtin/branch.c:848 msgid "edit the description for the branch" msgstr "изменить описание ветки" -#: builtin/branch.c:848 +#: builtin/branch.c:849 msgid "force creation, move/rename, deletion" msgstr "принудительное создание, перемещение или удаление ветки" -#: builtin/branch.c:851 +#: builtin/branch.c:852 msgid "print only not merged branches" msgstr "вывод только не слитых веток" -#: builtin/branch.c:857 +#: builtin/branch.c:858 msgid "print only merged branches" msgstr "вывод только слитых веток" -#: builtin/branch.c:861 +#: builtin/branch.c:862 msgid "list branches in columns" msgstr "показать список веток по столбцам" -#: builtin/branch.c:874 +#: builtin/branch.c:875 msgid "Failed to resolve HEAD as a valid ref." msgstr "Не удалось определить HEAD как действительную ссылку." -#: builtin/branch.c:878 builtin/clone.c:622 +#: builtin/branch.c:879 builtin/clone.c:690 msgid "HEAD not found below refs/heads!" msgstr "HEAD не найден в refs/heads!" -#: builtin/branch.c:900 +#: builtin/branch.c:901 msgid "--column and --verbose are incompatible" msgstr "--column и --verbose нельзя использовать одновременно" -#: builtin/branch.c:911 builtin/branch.c:950 +#: builtin/branch.c:912 builtin/branch.c:951 msgid "branch name required" msgstr "требуется имя ветки" -#: builtin/branch.c:926 +#: builtin/branch.c:927 msgid "Cannot give description to detached HEAD" msgstr "Нельзя дать описание отделенному HEAD" -#: builtin/branch.c:931 +#: builtin/branch.c:932 msgid "cannot edit description of more than one branch" msgstr "нельзя изменить описание более одной ветки за раз" -#: builtin/branch.c:938 +#: builtin/branch.c:939 #, c-format msgid "No commit on branch '%s' yet." msgstr "Еще нет коммита на ветке «%s»." -#: builtin/branch.c:941 +#: builtin/branch.c:942 #, c-format msgid "No branch named '%s'." msgstr "Нет ветки с именем «%s»." -#: builtin/branch.c:956 +#: builtin/branch.c:957 msgid "too many branches for a rename operation" msgstr "слишком много веток для операции переименования" -#: builtin/branch.c:961 +#: builtin/branch.c:962 msgid "too many branches to set new upstream" msgstr "слишком много веток для указания новых вышестоящих" -#: builtin/branch.c:965 +#: builtin/branch.c:966 #, c-format msgid "" "could not set upstream of HEAD to %s when it does not point to any branch." msgstr "невозможно установить вышестоящий репозиторий для HEAD на %s, когда он не указывает ни на одну ветку." -#: builtin/branch.c:968 builtin/branch.c:990 builtin/branch.c:1011 +#: builtin/branch.c:969 builtin/branch.c:991 builtin/branch.c:1012 #, c-format msgid "no such branch '%s'" msgstr "нет такой ветки «%s»" -#: builtin/branch.c:972 +#: builtin/branch.c:973 #, c-format msgid "branch '%s' does not exist" msgstr "ветка «%s» не существует" -#: builtin/branch.c:984 +#: builtin/branch.c:985 msgid "too many branches to unset upstream" msgstr "слишком много веток для убирания вышестоящих" -#: builtin/branch.c:988 +#: builtin/branch.c:989 msgid "could not unset upstream of HEAD when it does not point to any branch." msgstr "невозможно убрать вышестоящий репозиторий для HEAD, когда он не указывает ни на одну ветку." -#: builtin/branch.c:994 +#: builtin/branch.c:995 #, c-format msgid "Branch '%s' has no upstream information" msgstr "Ветка «%s» не имеет информации о вышестоящей ветке" -#: builtin/branch.c:1008 +#: builtin/branch.c:1009 msgid "it does not make sense to create 'HEAD' manually" msgstr "не имеет смысла создавать «HEAD» вручную" -#: builtin/branch.c:1014 +#: builtin/branch.c:1015 msgid "-a and -r options to 'git branch' do not make sense with a branch name" msgstr "параметры -a и -r для «git branch» не имеют смысла с указанием имени ветки" -#: builtin/branch.c:1017 +#: builtin/branch.c:1018 #, c-format msgid "" "The --set-upstream flag is deprecated and will be removed. Consider using " "--track or --set-upstream-to\n" msgstr "Флаг --set-upstream устарел и будет удален в будущем. Вместо него используйте --track или --set-upstream-to\n" -#: builtin/branch.c:1034 +#: builtin/branch.c:1035 #, c-format msgid "" "\n" @@ -3164,12 +3633,12 @@ msgid "" "\n" msgstr "\nЕсли вы хотите, чтобы «%s» отслеживала «%s», сделайте следующее:\n\n" -#: builtin/branch.c:1035 +#: builtin/branch.c:1036 #, c-format msgid " git branch -d %s\n" msgstr "git branch -d %s\n" -#: builtin/branch.c:1036 +#: builtin/branch.c:1037 #, c-format msgid " git branch --set-upstream-to %s\n" msgstr " git branch --set-upstream-to %s\n" @@ -3187,58 +3656,66 @@ msgstr "Требуется репозиторий для создания пак msgid "Need a repository to unbundle." msgstr "Требуется репозиторий для распаковки." -#: builtin/cat-file.c:369 +#: builtin/cat-file.c:428 msgid "" "git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-" "type]|-e|-p||--textconv) " msgstr "git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<тип>|--textconv) <объект>" -#: builtin/cat-file.c:370 +#: builtin/cat-file.c:429 msgid "" "git cat-file (--batch | --batch-check) [--follow-symlinks] < " msgstr "git cat-file (--batch | --batch-check) [--follow-symlinks] < <список-объектов>" -#: builtin/cat-file.c:407 +#: builtin/cat-file.c:466 msgid " can be one of: blob, tree, commit, tag" msgstr "<тип> может быть одним из: blob, tree, commit, tag" -#: builtin/cat-file.c:408 +#: builtin/cat-file.c:467 msgid "show object type" msgstr "показать тип объекта" -#: builtin/cat-file.c:409 +#: builtin/cat-file.c:468 msgid "show object size" msgstr "показать размер объекта" -#: builtin/cat-file.c:411 +#: builtin/cat-file.c:470 msgid "exit with zero when there's no error" msgstr "выйти с нулевым кодом возврата, если нет ошибки" -#: builtin/cat-file.c:412 +#: builtin/cat-file.c:471 msgid "pretty-print object's content" msgstr "структурированный вывод содержимого объекта" -#: builtin/cat-file.c:414 +#: builtin/cat-file.c:473 msgid "for blob objects, run textconv on object's content" msgstr "запустить texconv на содержимом двоичных объектов " -#: builtin/cat-file.c:416 +#: builtin/cat-file.c:475 msgid "allow -s and -t to work with broken/corrupt objects" msgstr "разрешить -s и -t работать с повреждёнными объектами" -#: builtin/cat-file.c:418 +#: builtin/cat-file.c:476 +msgid "buffer --batch output" +msgstr "буфферировать вывод --batch" + +#: builtin/cat-file.c:478 msgid "show info and content of objects fed from the standard input" msgstr "показать информацию и содержимое объектов, переданных из стандартного ввода" -#: builtin/cat-file.c:421 +#: builtin/cat-file.c:481 msgid "show info about objects fed from the standard input" msgstr "показать информацию об объектах, переданных из стандартного ввода" -#: builtin/cat-file.c:424 +#: builtin/cat-file.c:484 msgid "follow in-tree symlinks (used with --batch or --batch-check)" msgstr "переходить по символьным ссылкам внутри дерева (используется с опциями --batch и --batch-check)" +#: builtin/cat-file.c:486 +msgid "show all objects with --batch or --batch-check" +msgstr "показать все объекты с опциями --batch или --batch-check" + #: builtin/check-attr.c:11 msgid "git check-attr [-a | --all | ...] [--] ..." msgstr "git check-attr [-a | --all | <атрибут>…] [--] <путь>…" @@ -3263,7 +3740,7 @@ msgstr "прочитать имена файлов из стандартного msgid "terminate input and output records by a NUL character" msgstr "окончание ввода и вывода записей по НУЛЕВОМУ символу" -#: builtin/check-ignore.c:18 builtin/checkout.c:1202 builtin/gc.c:279 +#: builtin/check-ignore.c:18 builtin/checkout.c:1133 builtin/gc.c:267 msgid "suppress progress reporting" msgstr "не выводить прогресс выполнения" @@ -3360,113 +3837,113 @@ msgstr "добавить спереди <строку> при создании msgid "copy out the files from named stage" msgstr "копировать файлы из указанного индекса" -#: builtin/checkout.c:24 +#: builtin/checkout.c:25 msgid "git checkout [] " msgstr "git checkout [<опции>] <ветка>" -#: builtin/checkout.c:25 +#: builtin/checkout.c:26 msgid "git checkout [] [] -- ..." msgstr "git checkout [<опции>] [<ветка>] -- <файл>…" -#: builtin/checkout.c:134 builtin/checkout.c:167 +#: builtin/checkout.c:133 builtin/checkout.c:166 #, c-format msgid "path '%s' does not have our version" msgstr "путь «%s» не имеет нашей версии" -#: builtin/checkout.c:136 builtin/checkout.c:169 +#: builtin/checkout.c:135 builtin/checkout.c:168 #, c-format msgid "path '%s' does not have their version" msgstr "путь «%s» не имеет их версии" -#: builtin/checkout.c:152 +#: builtin/checkout.c:151 #, c-format msgid "path '%s' does not have all necessary versions" msgstr "путь «%s» не имеет всех необходимых версий" -#: builtin/checkout.c:196 +#: builtin/checkout.c:195 #, c-format msgid "path '%s' does not have necessary versions" msgstr "путь «%s» не имеет необходимых версий" -#: builtin/checkout.c:213 +#: builtin/checkout.c:212 #, c-format msgid "path '%s': cannot merge" msgstr "путь «%s»: не удалось слить" -#: builtin/checkout.c:230 +#: builtin/checkout.c:229 #, c-format msgid "Unable to add merge result for '%s'" msgstr "Не удалось добавить результат слияния «%s»" -#: builtin/checkout.c:251 builtin/checkout.c:254 builtin/checkout.c:257 -#: builtin/checkout.c:260 +#: builtin/checkout.c:250 builtin/checkout.c:253 builtin/checkout.c:256 +#: builtin/checkout.c:259 #, c-format msgid "'%s' cannot be used with updating paths" msgstr "«%s» нельзя использовать при обновлении путей" -#: builtin/checkout.c:263 builtin/checkout.c:266 +#: builtin/checkout.c:262 builtin/checkout.c:265 #, c-format msgid "'%s' cannot be used with %s" msgstr "«%s» нельзя использовать одновременно с %s" -#: builtin/checkout.c:269 +#: builtin/checkout.c:268 #, c-format msgid "Cannot update paths and switch to branch '%s' at the same time." msgstr "Нельзя обновлять пути и переключаться на ветку «%s» одновременно." -#: builtin/checkout.c:280 builtin/checkout.c:474 +#: builtin/checkout.c:279 builtin/checkout.c:473 msgid "corrupt index file" msgstr "файл индекса поврежден" -#: builtin/checkout.c:340 builtin/checkout.c:347 +#: builtin/checkout.c:339 builtin/checkout.c:346 #, c-format msgid "path '%s' is unmerged" msgstr "путь «%s» не слит" -#: builtin/checkout.c:496 +#: builtin/checkout.c:495 msgid "you need to resolve your current index first" msgstr "сначала нужно разрешить конфликты в вашем текущем индексе" -#: builtin/checkout.c:627 +#: builtin/checkout.c:622 #, c-format -msgid "Can not do reflog for '%s'\n" -msgstr "Не удалось создать журнал ссылок для «%s»\n" +msgid "Can not do reflog for '%s': %s\n" +msgstr "Не удалось создать журнал ссылок для «%s»': %s\n" -#: builtin/checkout.c:663 +#: builtin/checkout.c:660 msgid "HEAD is now at" msgstr "HEAD сейчас на" -#: builtin/checkout.c:670 +#: builtin/checkout.c:667 #, c-format msgid "Reset branch '%s'\n" msgstr "Сброс ветки «%s»\n" -#: builtin/checkout.c:673 +#: builtin/checkout.c:670 #, c-format msgid "Already on '%s'\n" msgstr "Уже на «%s»\n" -#: builtin/checkout.c:677 +#: builtin/checkout.c:674 #, c-format msgid "Switched to and reset branch '%s'\n" msgstr "Переключение и сброс ветки «%s»\n" -#: builtin/checkout.c:679 builtin/checkout.c:1134 +#: builtin/checkout.c:676 builtin/checkout.c:1065 #, c-format msgid "Switched to a new branch '%s'\n" msgstr "Переключено на новую ветку «%s»\n" -#: builtin/checkout.c:681 +#: builtin/checkout.c:678 #, c-format msgid "Switched to branch '%s'\n" msgstr "Переключено на ветку «%s»\n" -#: builtin/checkout.c:733 +#: builtin/checkout.c:730 #, c-format msgid " ... and %d more.\n" msgstr " … и еще %d.\n" -#: builtin/checkout.c:739 +#: builtin/checkout.c:736 #, c-format msgid "" "Warning: you are leaving %d commit behind, not connected to\n" @@ -3483,7 +3960,7 @@ msgstr[1] "Предупреждение: вы оставляете позади msgstr[2] "Предупреждение: вы оставляете позади %d коммитов не соединенные ни с одной из ваших веток:\n\n%s\n" msgstr[3] "Предупреждение: вы оставляете позади %d коммитов не соединенные ни с одной из ваших веток:\n\n%s\n" -#: builtin/checkout.c:758 +#: builtin/checkout.c:755 #, c-format msgid "" "If you want to keep it by creating a new branch, this may be a good time\n" @@ -3502,197 +3979,192 @@ msgstr[1] "Если вы хотите сохранить их с помощью msgstr[2] "Если вы хотите сохранить их с помощью создания новой ветки, то сейчас самое время\nсделать это с помощью:\n\n git branch <имя-новой-ветки> %s\n\n" msgstr[3] "Если вы хотите сохранить их с помощью создания новой ветки, то сейчас самое время\nсделать это с помощью:\n\n git branch <имя-новой-ветки> %s\n\n" -#: builtin/checkout.c:794 +#: builtin/checkout.c:791 msgid "internal error in revision walk" msgstr "внутренняя ошибка при хождении по редакциям" -#: builtin/checkout.c:798 +#: builtin/checkout.c:795 msgid "Previous HEAD position was" msgstr "Предыдущая позиция HEAD была" -#: builtin/checkout.c:825 builtin/checkout.c:1129 +#: builtin/checkout.c:822 builtin/checkout.c:1060 msgid "You are on a branch yet to be born" msgstr "Вы находитесь на еще не созданной ветке" -#: builtin/checkout.c:931 -#, c-format -msgid "'%s' is already checked out at '%s'" -msgstr "«%s» уже находится на «%s»" - -#: builtin/checkout.c:1036 +#: builtin/checkout.c:967 #, c-format msgid "only one reference expected, %d given." msgstr "ожидается только одна ссылка, а передано %d." -#: builtin/checkout.c:1075 +#: builtin/checkout.c:1006 builtin/worktree.c:210 #, c-format msgid "invalid reference: %s" msgstr "неправильная ссылка: %s" -#: builtin/checkout.c:1104 +#: builtin/checkout.c:1035 #, c-format msgid "reference is not a tree: %s" msgstr "в дереве нет такой ссылки: %s" -#: builtin/checkout.c:1143 +#: builtin/checkout.c:1074 msgid "paths cannot be used with switching branches" msgstr "нельзя использовать пути при переключении веток" -#: builtin/checkout.c:1146 builtin/checkout.c:1150 +#: builtin/checkout.c:1077 builtin/checkout.c:1081 #, c-format msgid "'%s' cannot be used with switching branches" msgstr "нельзя использовать «%s» при переключении веток" -#: builtin/checkout.c:1154 builtin/checkout.c:1157 builtin/checkout.c:1162 -#: builtin/checkout.c:1165 +#: builtin/checkout.c:1085 builtin/checkout.c:1088 builtin/checkout.c:1093 +#: builtin/checkout.c:1096 #, c-format msgid "'%s' cannot be used with '%s'" msgstr "«%s» нельзя использовать одновременно с «%s»" -#: builtin/checkout.c:1170 +#: builtin/checkout.c:1101 #, c-format msgid "Cannot switch branch to a non-commit '%s'" msgstr "Нельзя переключить ветку на не коммит «%s»" -#: builtin/checkout.c:1203 builtin/checkout.c:1205 builtin/clone.c:83 -#: builtin/remote.c:159 builtin/remote.c:161 builtin/worktree.c:282 -#: builtin/worktree.c:284 +#: builtin/checkout.c:1134 builtin/checkout.c:1136 builtin/clone.c:83 +#: builtin/remote.c:159 builtin/remote.c:161 builtin/worktree.c:317 +#: builtin/worktree.c:319 msgid "branch" msgstr "ветка" -#: builtin/checkout.c:1204 +#: builtin/checkout.c:1135 msgid "create and checkout a new branch" msgstr "создать и перейти на новую ветку" -#: builtin/checkout.c:1206 +#: builtin/checkout.c:1137 msgid "create/reset and checkout a branch" msgstr "создать/сбросить и перейти на новую ветку" -#: builtin/checkout.c:1207 +#: builtin/checkout.c:1138 msgid "create reflog for new branch" msgstr "создать журнал ссылок для новой ветки" -#: builtin/checkout.c:1208 +#: builtin/checkout.c:1139 msgid "detach the HEAD at named commit" msgstr "отсоединить HEAD на указанном коммите" -#: builtin/checkout.c:1209 +#: builtin/checkout.c:1140 msgid "set upstream info for new branch" msgstr "установить информацию о вышестоящей ветке для новой ветки" -#: builtin/checkout.c:1211 +#: builtin/checkout.c:1142 msgid "new-branch" msgstr "новая-ветка" -#: builtin/checkout.c:1211 +#: builtin/checkout.c:1142 msgid "new unparented branch" msgstr "новая ветка без родителей" -#: builtin/checkout.c:1212 +#: builtin/checkout.c:1143 msgid "checkout our version for unmerged files" msgstr "перейти на нашу версию для не слитых файлов" -#: builtin/checkout.c:1214 +#: builtin/checkout.c:1145 msgid "checkout their version for unmerged files" msgstr "перейти на их версию для не слитых файлов" -#: builtin/checkout.c:1216 +#: builtin/checkout.c:1147 msgid "force checkout (throw away local modifications)" msgstr "принудительный переход (отбрасывает все локальные изменения)" -#: builtin/checkout.c:1217 +#: builtin/checkout.c:1148 msgid "perform a 3-way merge with the new branch" msgstr "выполнить трехходовое слияние с новой веткой" -#: builtin/checkout.c:1218 builtin/merge.c:227 +#: builtin/checkout.c:1149 builtin/merge.c:227 msgid "update ignored files (default)" msgstr "обновить игнорируемые файлы (по умолчанию)" -#: builtin/checkout.c:1219 builtin/log.c:1239 parse-options.h:244 +#: builtin/checkout.c:1150 builtin/log.c:1264 parse-options.h:249 msgid "style" msgstr "стиль" -#: builtin/checkout.c:1220 +#: builtin/checkout.c:1151 msgid "conflict style (merge or diff3)" msgstr "стиль конфликтов слияния (merge или diff3)" -#: builtin/checkout.c:1223 +#: builtin/checkout.c:1154 msgid "do not limit pathspecs to sparse entries only" msgstr "не ограничивать спецификаторы пути только частичными записями" -#: builtin/checkout.c:1225 +#: builtin/checkout.c:1156 msgid "second guess 'git checkout '" msgstr "пересмотр «git checkout »" -#: builtin/checkout.c:1227 +#: builtin/checkout.c:1158 msgid "do not check if another worktree is holding the given ref" msgstr "не проверять, что другое дерево уже содержит указанную ссылку" -#: builtin/checkout.c:1252 +#: builtin/checkout.c:1181 msgid "-b, -B and --orphan are mutually exclusive" msgstr "-b, -B и --orphan нельзя использовать одновременно" -#: builtin/checkout.c:1269 +#: builtin/checkout.c:1198 msgid "--track needs a branch name" msgstr "--track требует имя ветки" -#: builtin/checkout.c:1274 +#: builtin/checkout.c:1203 msgid "Missing branch name; try -b" msgstr "Пропущено имя ветки; попробуйте -b" -#: builtin/checkout.c:1310 +#: builtin/checkout.c:1239 msgid "invalid path specification" msgstr "неправильная спецификация пути" -#: builtin/checkout.c:1317 +#: builtin/checkout.c:1246 #, c-format msgid "" "Cannot update paths and switch to branch '%s' at the same time.\n" "Did you intend to checkout '%s' which can not be resolved as commit?" msgstr "Нельзя обновить пути и одновременно переключить на ветку «%s».\nВы хотели переключиться на «%s», что не может быть определено как коммит?" -#: builtin/checkout.c:1322 +#: builtin/checkout.c:1251 #, c-format msgid "git checkout: --detach does not take a path argument '%s'" msgstr "git checkout: --detach не принимает путь «%s» как аргумент" -#: builtin/checkout.c:1326 +#: builtin/checkout.c:1255 msgid "" "git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index." msgstr "git checkout: --ours/--theirs, --force and --merge нельзя использовать одновременно при применении состояния индекса." -#: builtin/clean.c:26 +#: builtin/clean.c:25 msgid "" "git clean [-d] [-f] [-i] [-n] [-q] [-e ] [-x | -X] [--] ..." msgstr "git clean [-d] [-f] [-i] [-n] [-q] [-e <шаблон>] [-x | -X] [--] <пути>…" -#: builtin/clean.c:30 +#: builtin/clean.c:29 #, c-format msgid "Removing %s\n" msgstr "Удаление %s\n" -#: builtin/clean.c:31 +#: builtin/clean.c:30 #, c-format msgid "Would remove %s\n" msgstr "Будет удалено %s\n" -#: builtin/clean.c:32 +#: builtin/clean.c:31 #, c-format msgid "Skipping repository %s\n" msgstr "Пропуск репозитория %s\n" -#: builtin/clean.c:33 +#: builtin/clean.c:32 #, c-format msgid "Would skip repository %s\n" msgstr "Будет пропущен репозиторий %s\n" -#: builtin/clean.c:34 +#: builtin/clean.c:33 #, c-format msgid "failed to remove %s" msgstr "сбой удаления %s" -#: builtin/clean.c:295 +#: builtin/clean.c:317 msgid "" "Prompt help:\n" "1 - select a numbered item\n" @@ -3700,7 +4172,7 @@ msgid "" " - (empty) select nothing" msgstr "Справка по выделению:\n1 - выбрать указанный элемент\nfoo - выбрать элемент с указанным префиксом\n - (пусто) не выбирать ничего" -#: builtin/clean.c:299 +#: builtin/clean.c:321 msgid "" "Prompt help:\n" "1 - select a single item\n" @@ -3712,36 +4184,36 @@ msgid "" " - (empty) finish selecting" msgstr "Справка по выделению:\n1 - выбрать один элемент\n3-5 - выбрать диапазон элементов\n2-3,6-9 - выбрать несколько диапазонов\nfoo - выбрать элемент с указанным префиксом\n-… - убрать выделение с указанных элементов\n* - выбрать все элементы\n - (пусто) завершить выделение" -#: builtin/clean.c:515 +#: builtin/clean.c:537 #, c-format msgid "Huh (%s)?" msgstr "Хм (%s)?" -#: builtin/clean.c:657 +#: builtin/clean.c:679 #, c-format msgid "Input ignore patterns>> " msgstr "Шаблоны игнорирования ввода>> " -#: builtin/clean.c:694 +#: builtin/clean.c:716 #, c-format msgid "WARNING: Cannot find items matched by: %s" msgstr "ПРЕДУПРЕЖДЕНИЕ: Не удалось найти элементы соответствующие: %s" -#: builtin/clean.c:715 +#: builtin/clean.c:737 msgid "Select items to delete" msgstr "Укажите элементы для удаления" #. TRANSLATORS: Make sure to keep [y/N] as is -#: builtin/clean.c:756 +#: builtin/clean.c:778 #, c-format msgid "Remove %s [y/N]? " msgstr "Удалить %s [y - да/N - нет]? " -#: builtin/clean.c:781 +#: builtin/clean.c:803 msgid "Bye." msgstr "До свидания." -#: builtin/clean.c:789 +#: builtin/clean.c:811 msgid "" "clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" @@ -3752,15 +4224,15 @@ msgid "" "? - help for prompt selection" msgstr "clean - начать очистку\nfilter by pattern - исключить удаление элементов\nselect by numbers - исключить удаление элементов по номерам\nask each - запрашивать подтверждение на удаление каждого элемента (как «rm -i»)\nquit - прекратить очистку\nhelp - этот экран\n? - справка по выделению" -#: builtin/clean.c:816 +#: builtin/clean.c:838 msgid "*** Commands ***" msgstr "*** Команды ***" -#: builtin/clean.c:817 +#: builtin/clean.c:839 msgid "What now" msgstr "Что теперь" -#: builtin/clean.c:825 +#: builtin/clean.c:847 msgid "Would remove the following item:" msgid_plural "Would remove the following items:" msgstr[0] "Удалить следующие элементы:" @@ -3768,54 +4240,54 @@ msgstr[1] "Удалить следующие элементы:" msgstr[2] "Удалить следующие элементы:" msgstr[3] "Удалить следующие элементы:" -#: builtin/clean.c:842 +#: builtin/clean.c:864 msgid "No more files to clean, exiting." msgstr "Больше нет файлов для очистки, выходим." -#: builtin/clean.c:873 +#: builtin/clean.c:895 msgid "do not print names of files removed" msgstr "не выводить имена удаляемых файлов" -#: builtin/clean.c:875 +#: builtin/clean.c:897 msgid "force" msgstr "принудительно" -#: builtin/clean.c:876 +#: builtin/clean.c:898 msgid "interactive cleaning" msgstr "интерактивная очистка" -#: builtin/clean.c:878 +#: builtin/clean.c:900 msgid "remove whole directories" msgstr "удалить каталоги полностью" -#: builtin/clean.c:879 builtin/describe.c:407 builtin/grep.c:714 +#: builtin/clean.c:901 builtin/describe.c:407 builtin/grep.c:714 #: builtin/ls-files.c:443 builtin/name-rev.c:311 builtin/show-ref.c:187 msgid "pattern" msgstr "шаблон" -#: builtin/clean.c:880 +#: builtin/clean.c:902 msgid "add to ignore rules" msgstr "добавить <шаблон> в правила игнорирования" -#: builtin/clean.c:881 +#: builtin/clean.c:903 msgid "remove ignored files, too" msgstr "также удалить игнорируемые файлы" -#: builtin/clean.c:883 +#: builtin/clean.c:905 msgid "remove only ignored files" msgstr "удалить только игнорируемые файлы" -#: builtin/clean.c:901 +#: builtin/clean.c:923 msgid "-x and -X cannot be used together" msgstr "нельзя использовать одновременно -x и -X" -#: builtin/clean.c:905 +#: builtin/clean.c:927 msgid "" "clean.requireForce set to true and neither -i, -n, nor -f given; refusing to" " clean" msgstr "clean.requireForce установлен как true и ни одна из опций -i, -n или -f не указана; отказ очистки" -#: builtin/clean.c:908 +#: builtin/clean.c:930 msgid "" "clean.requireForce defaults to true and neither -i, -n, nor -f given; " "refusing to clean" @@ -3825,8 +4297,8 @@ msgstr "clean.requireForce установлен по умолчанию как t msgid "git clone [] [--] []" msgstr "git clone [<опции>] [--] <репозиторий> [<каталог>]" -#: builtin/clone.c:57 builtin/fetch.c:111 builtin/merge.c:224 -#: builtin/push.c:523 +#: builtin/clone.c:57 builtin/fetch.c:112 builtin/merge.c:224 +#: builtin/pull.c:109 builtin/push.c:560 builtin/send-pack.c:168 msgid "force progress reporting" msgstr "принудительно выводить прогресс" @@ -3834,7 +4306,7 @@ msgstr "принудительно выводить прогресс" msgid "don't create a checkout" msgstr "не переключать рабочую копию на HEAD" -#: builtin/clone.c:60 builtin/clone.c:62 builtin/init-db.c:503 +#: builtin/clone.c:60 builtin/clone.c:62 builtin/init-db.c:504 msgid "create a bare repository" msgstr "создать голый репозиторий" @@ -3858,11 +4330,11 @@ msgstr "настроить как общедоступный репозитор msgid "initialize submodules in the clone" msgstr "инициализировать подмодули в клоне" -#: builtin/clone.c:75 builtin/init-db.c:500 +#: builtin/clone.c:75 builtin/init-db.c:501 msgid "template-directory" msgstr "каталог-шаблонов" -#: builtin/clone.c:76 builtin/init-db.c:501 +#: builtin/clone.c:76 builtin/init-db.c:502 msgid "directory from which templates will be used" msgstr "каталог, шаблоны из которого будут использованы" @@ -3890,7 +4362,8 @@ msgstr "перейти на <ветку>, вместо HEAD внешнего р msgid "path to git-upload-pack on the remote" msgstr "путь к git-upload-pack на внешнем репозитории" -#: builtin/clone.c:87 builtin/fetch.c:112 builtin/grep.c:659 +#: builtin/clone.c:87 builtin/fetch.c:113 builtin/grep.c:659 +#: builtin/pull.c:186 msgid "depth" msgstr "глубина" @@ -3902,11 +4375,11 @@ msgstr "сделать частичный клон указанной глуби msgid "clone only one branch, HEAD or --branch" msgstr "клонировать только одну ветку, HEAD или --branch" -#: builtin/clone.c:91 builtin/init-db.c:509 +#: builtin/clone.c:91 builtin/init-db.c:510 msgid "gitdir" msgstr "каталог-git" -#: builtin/clone.c:92 builtin/init-db.c:510 +#: builtin/clone.c:92 builtin/init-db.c:511 msgid "separate git dir from working tree" msgstr "разместить каталог git отдельно от рабочей копии" @@ -3918,178 +4391,173 @@ msgstr "ключ=значение" msgid "set config inside the new repository" msgstr "установить параметры внутри нового репозитория" -#: builtin/clone.c:240 +#: builtin/clone.c:298 #, c-format msgid "reference repository '%s' is not a local repository." msgstr "ссылаемый репозиторий «%s» не является локальным." -#: builtin/clone.c:244 +#: builtin/clone.c:302 #, c-format msgid "reference repository '%s' is shallow" msgstr "ссылаемый репозиторий «%s» является частичным" -#: builtin/clone.c:247 +#: builtin/clone.c:305 #, c-format msgid "reference repository '%s' is grafted" msgstr "ссылаемый репозиторий «%s» является сращенным" -#: builtin/clone.c:310 -#, c-format -msgid "failed to create directory '%s'" -msgstr "не удалось создать каталог «%s»" - -#: builtin/clone.c:312 builtin/diff.c:84 +#: builtin/clone.c:370 builtin/diff.c:84 #, c-format msgid "failed to stat '%s'" msgstr "не удалось выполнить stat «%s»" -#: builtin/clone.c:314 +#: builtin/clone.c:372 #, c-format msgid "%s exists and is not a directory" msgstr "%s уже существует и не является каталогом" -#: builtin/clone.c:328 +#: builtin/clone.c:386 #, c-format msgid "failed to stat %s\n" msgstr "не удалось выполнить stat %s\n" -#: builtin/clone.c:350 +#: builtin/clone.c:408 #, c-format msgid "failed to create link '%s'" msgstr "не удалось создать ссылку «%s»" -#: builtin/clone.c:354 +#: builtin/clone.c:412 #, c-format msgid "failed to copy file to '%s'" msgstr "не удалось копировать файл в «%s»" -#: builtin/clone.c:377 builtin/clone.c:551 +#: builtin/clone.c:435 builtin/clone.c:619 #, c-format msgid "done.\n" msgstr "готово.\n" -#: builtin/clone.c:389 +#: builtin/clone.c:447 msgid "" "Clone succeeded, but checkout failed.\n" "You can inspect what was checked out with 'git status'\n" "and retry the checkout with 'git checkout -f HEAD'\n" msgstr "Клонирование прошло успешно, но во время перехода на версию произошла ошибка.\nС помощь команды «git status» вы можете просмотреть, какие файлы были обновлены, а повторить попытку перехода на версию с помощью «git checkout -f HEAD»\n" -#: builtin/clone.c:466 +#: builtin/clone.c:524 #, c-format msgid "Could not find remote branch %s to clone." msgstr "Не удалось найти внешнюю ветку %s для клонирования." -#: builtin/clone.c:546 +#: builtin/clone.c:614 #, c-format msgid "Checking connectivity... " msgstr "Проверка соединения… " -#: builtin/clone.c:549 +#: builtin/clone.c:617 msgid "remote did not send all necessary objects" msgstr "внешний репозиторий прислал не все необходимые объекты" -#: builtin/clone.c:613 +#: builtin/clone.c:681 msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n" msgstr "внешний HEAD ссылается на несуществующую ссылку, нельзя перейти на такую версию.\n" -#: builtin/clone.c:644 +#: builtin/clone.c:712 msgid "unable to checkout working tree" msgstr "не удалось перейти на версию в рабочем каталоге" -#: builtin/clone.c:731 +#: builtin/clone.c:799 msgid "cannot repack to clean up" msgstr "не удалось выполнить перепаковку для очистки" -#: builtin/clone.c:733 +#: builtin/clone.c:801 msgid "cannot unlink temporary alternates file" msgstr "не удалось отсоединить временные альтернативные файлы" -#: builtin/clone.c:763 +#: builtin/clone.c:831 msgid "Too many arguments." msgstr "Слишком много аргументов." -#: builtin/clone.c:767 +#: builtin/clone.c:835 msgid "You must specify a repository to clone." msgstr "Вы должны указать репозиторий для клонирования." -#: builtin/clone.c:778 +#: builtin/clone.c:846 #, c-format msgid "--bare and --origin %s options are incompatible." msgstr "--bare и --origin %s нельзя использовать одновременно." -#: builtin/clone.c:781 +#: builtin/clone.c:849 msgid "--bare and --separate-git-dir are incompatible." msgstr "--bare и --separate-git-dir нельзя использовать одновременно." -#: builtin/clone.c:794 +#: builtin/clone.c:862 #, c-format msgid "repository '%s' does not exist" msgstr "репозиторий «%s» не существует" -#: builtin/clone.c:800 builtin/fetch.c:1160 +#: builtin/clone.c:868 builtin/fetch.c:1168 #, c-format msgid "depth %s is not a positive number" msgstr "глубина %s не является положительным числом" -#: builtin/clone.c:810 +#: builtin/clone.c:878 #, c-format msgid "destination path '%s' already exists and is not an empty directory." msgstr "целевой путь «%s» уже существует и не является пустым каталогом." -#: builtin/clone.c:820 +#: builtin/clone.c:888 #, c-format msgid "working tree '%s' already exists." msgstr "рабочий каталог «%s» уже существует." -#: builtin/clone.c:835 builtin/clone.c:846 builtin/worktree.c:193 -#: builtin/worktree.c:220 +#: builtin/clone.c:903 builtin/clone.c:914 builtin/worktree.c:218 +#: builtin/worktree.c:245 #, c-format msgid "could not create leading directories of '%s'" msgstr "не удалось создать родительские каталоги для «%s»" -#: builtin/clone.c:838 +#: builtin/clone.c:906 #, c-format msgid "could not create work tree dir '%s'" msgstr "не удалось создать рабочий каталог «%s»" -#: builtin/clone.c:856 +#: builtin/clone.c:924 #, c-format msgid "Cloning into bare repository '%s'...\n" msgstr "Клонирование в голый репозиторий «%s»…\n" -#: builtin/clone.c:858 +#: builtin/clone.c:926 #, c-format msgid "Cloning into '%s'...\n" msgstr "Клонирование в «%s»…\n" -#: builtin/clone.c:883 +#: builtin/clone.c:951 msgid "--dissociate given, but there is no --reference" msgstr "передана опция --dissociate, но не передана --reference" -#: builtin/clone.c:900 +#: builtin/clone.c:968 msgid "--depth is ignored in local clones; use file:// instead." msgstr "--depth игнорируется на локальных клонах; вместо этого используйте file://." -#: builtin/clone.c:903 +#: builtin/clone.c:971 msgid "source repository is shallow, ignoring --local" msgstr "исходный репозиторий является частичным, --local игнорируется" -#: builtin/clone.c:908 +#: builtin/clone.c:976 msgid "--local is ignored" msgstr "--local игнорируется" -#: builtin/clone.c:912 +#: builtin/clone.c:980 #, c-format msgid "Don't know how to clone %s" msgstr "Не знаю как клонировать %s" -#: builtin/clone.c:961 builtin/clone.c:969 +#: builtin/clone.c:1029 builtin/clone.c:1037 #, c-format msgid "Remote branch %s not found in upstream %s" msgstr "Внешняя ветка %s не найдена в вышестоящем репозитории %s" -#: builtin/clone.c:972 +#: builtin/clone.c:1040 msgid "You appear to have cloned an empty repository." msgstr "Похоже, что вы клонировали пустой репозиторий." @@ -4196,108 +4664,99 @@ msgstr "Если вы хотите пропустит этот коммит, и msgid "failed to unpack HEAD tree object" msgstr "сбой распаковки объекта дерева HEAD" -#: builtin/commit.c:344 +#: builtin/commit.c:345 msgid "unable to create temporary index" msgstr "не удалось создать временный индекс" -#: builtin/commit.c:350 +#: builtin/commit.c:351 msgid "interactive add failed" msgstr "сбой интерактивного добавления" -#: builtin/commit.c:361 -msgid "unable to write index file" -msgstr "не удалось записать индекс" - -#: builtin/commit.c:363 +#: builtin/commit.c:364 msgid "unable to update temporary index" msgstr "не удалось обновить временный индекс" -#: builtin/commit.c:365 +#: builtin/commit.c:366 msgid "Failed to update main cache tree" msgstr "Сбой при обновлении основного кэша дерева" -#: builtin/commit.c:389 builtin/commit.c:414 builtin/commit.c:463 +#: builtin/commit.c:390 builtin/commit.c:413 builtin/commit.c:462 msgid "unable to write new_index file" msgstr "не удалось записать файл new_index" -#: builtin/commit.c:445 +#: builtin/commit.c:444 msgid "cannot do a partial commit during a merge." msgstr "нельзя создать частичный коммит во время слияния." -#: builtin/commit.c:447 +#: builtin/commit.c:446 msgid "cannot do a partial commit during a cherry-pick." msgstr "нельзя создать частичный коммит во время отбора лучшего коммита." -#: builtin/commit.c:456 +#: builtin/commit.c:455 msgid "cannot read the index" msgstr "не удалось прочитать индекс" -#: builtin/commit.c:475 +#: builtin/commit.c:474 msgid "unable to write temporary index file" msgstr "не удалось записать временный файл индекса" -#: builtin/commit.c:580 +#: builtin/commit.c:579 #, c-format msgid "commit '%s' lacks author header" msgstr "у коммита «%s» отсутствует автор в заголовке" -#: builtin/commit.c:582 +#: builtin/commit.c:581 #, c-format msgid "commit '%s' has malformed author line" msgstr "у коммита «%s» автор в неверном формате" -#: builtin/commit.c:601 +#: builtin/commit.c:600 msgid "malformed --author parameter" msgstr "параметр --author в неверном формате" -#: builtin/commit.c:609 +#: builtin/commit.c:608 #, c-format msgid "invalid date format: %s" msgstr "неправильный формат даты: %s" -#: builtin/commit.c:653 +#: builtin/commit.c:652 msgid "" "unable to select a comment character that is not used\n" "in the current commit message" msgstr "нельзя выбрать символ комментария, который\nне используется в текущем сообщении коммита" -#: builtin/commit.c:690 builtin/commit.c:723 builtin/commit.c:1080 +#: builtin/commit.c:689 builtin/commit.c:722 builtin/commit.c:1079 #, c-format msgid "could not lookup commit %s" msgstr "не удалось запросить коммит %s" -#: builtin/commit.c:702 builtin/shortlog.c:273 +#: builtin/commit.c:701 builtin/shortlog.c:273 #, c-format msgid "(reading log message from standard input)\n" msgstr "(чтение файла журнала из стандартного ввода)\n" -#: builtin/commit.c:704 +#: builtin/commit.c:703 msgid "could not read log from standard input" msgstr "не удалось прочитать файл журнала из стандартного ввода" -#: builtin/commit.c:708 +#: builtin/commit.c:707 #, c-format msgid "could not read log file '%s'" msgstr "не удалось прочитать файл журнала «%s»" -#: builtin/commit.c:730 +#: builtin/commit.c:729 msgid "could not read MERGE_MSG" msgstr "не удалось прочитать MERGE_MSG" -#: builtin/commit.c:734 +#: builtin/commit.c:733 msgid "could not read SQUASH_MSG" msgstr "не удалось прочитать SQUASH_MSG" -#: builtin/commit.c:738 builtin/merge.c:1079 -#, c-format -msgid "could not read '%s'" -msgstr "не удалось прочитать «%s»" - -#: builtin/commit.c:785 +#: builtin/commit.c:784 msgid "could not write commit template" msgstr "не удалось записать шаблон описания коммита" -#: builtin/commit.c:803 +#: builtin/commit.c:802 #, c-format msgid "" "\n" @@ -4307,7 +4766,7 @@ msgid "" "and try again.\n" msgstr "\nПохоже, что вы пытаетесь закоммитить слияние.\nЕсли это ошибка, пожалуйста удалите файл\n\t%s\nи попробуйте снова.\n" -#: builtin/commit.c:808 +#: builtin/commit.c:807 #, c-format msgid "" "\n" @@ -4317,14 +4776,14 @@ msgid "" "and try again.\n" msgstr "\nПохоже, что вы пытаетесь закоммитить отбор лучшего.\nЕсли это ошибка, пожалуйста удалите файл\n\t%s\nи попробуйте снова.\n" -#: builtin/commit.c:821 +#: builtin/commit.c:820 #, c-format msgid "" "Please enter the commit message for your changes. Lines starting\n" "with '%c' will be ignored, and an empty message aborts the commit.\n" msgstr "Пожалуйста, введите сообщение коммита для ваших изменений. Строки,\nначинающиеся с «%c» будут проигнорированы, а пустое сообщение\nотменяет процесс коммита.\n" -#: builtin/commit.c:828 +#: builtin/commit.c:827 #, c-format msgid "" "Please enter the commit message for your changes. Lines starting\n" @@ -4332,348 +4791,335 @@ msgid "" "An empty message aborts the commit.\n" msgstr "Пожалуйста, введите сообщение коммита для ваших изменений. Строки,\nначинающиеся с «%c» будут оставлены; вы можете удалить их вручную,\nесли хотите. Пустое сообщение отменяет процесс коммита.\n" -#: builtin/commit.c:848 +#: builtin/commit.c:847 #, c-format msgid "%sAuthor: %.*s <%.*s>" msgstr "%sАвтор: %.*s <%.*s>" -#: builtin/commit.c:856 +#: builtin/commit.c:855 #, c-format msgid "%sDate: %s" msgstr "%sДата: %s" -#: builtin/commit.c:863 +#: builtin/commit.c:862 #, c-format msgid "%sCommitter: %.*s <%.*s>" msgstr "%sКоммитер: %.*s <%.*s>" -#: builtin/commit.c:881 +#: builtin/commit.c:880 msgid "Cannot read index" msgstr "Не удалось прочитать индекс" -#: builtin/commit.c:938 +#: builtin/commit.c:937 msgid "Error building trees" msgstr "Ошибка при построении деревьев" -#: builtin/commit.c:953 builtin/tag.c:495 +#: builtin/commit.c:952 builtin/tag.c:495 #, c-format msgid "Please supply the message using either -m or -F option.\n" msgstr "Пожалуйста, укажите сообщение, при указании опций -m или -F.\n" -#: builtin/commit.c:1055 +#: builtin/commit.c:1054 #, c-format msgid "--author '%s' is not 'Name ' and matches no existing author" msgstr "--author «%s» не в формате «Имя <почта>» и не совпадает с существующим автором" -#: builtin/commit.c:1070 builtin/commit.c:1310 +#: builtin/commit.c:1069 builtin/commit.c:1309 #, c-format msgid "Invalid untracked files mode '%s'" msgstr "Неправильный режим неотслеживаемых файлов «%s»" -#: builtin/commit.c:1107 +#: builtin/commit.c:1106 msgid "--long and -z are incompatible" msgstr "--long и -z нельзя использовать одновременно" -#: builtin/commit.c:1137 +#: builtin/commit.c:1136 msgid "Using both --reset-author and --author does not make sense" msgstr "Указание одновременно опций --reset-author и --author не имеет смысла" -#: builtin/commit.c:1146 +#: builtin/commit.c:1145 msgid "You have nothing to amend." msgstr "Нечего исправлять." -#: builtin/commit.c:1149 +#: builtin/commit.c:1148 msgid "You are in the middle of a merge -- cannot amend." msgstr "Вы в процессе слияния — сейчас нельзя исправлять." -#: builtin/commit.c:1151 +#: builtin/commit.c:1150 msgid "You are in the middle of a cherry-pick -- cannot amend." msgstr "Вы в процессе отбора лучшего — сейчас нельзя исправлять." -#: builtin/commit.c:1154 +#: builtin/commit.c:1153 msgid "Options --squash and --fixup cannot be used together" msgstr "Опции --squash и --fixup не могут использоваться одновременно" -#: builtin/commit.c:1164 +#: builtin/commit.c:1163 msgid "Only one of -c/-C/-F/--fixup can be used." msgstr "Может использоваться только одна из опций -c/-C/-F/--fixup." -#: builtin/commit.c:1166 +#: builtin/commit.c:1165 msgid "Option -m cannot be combined with -c/-C/-F/--fixup." msgstr "Опция -m не может использоваться с -c/-C/-F/--fixup." -#: builtin/commit.c:1174 +#: builtin/commit.c:1173 msgid "--reset-author can be used only with -C, -c or --amend." msgstr "--reset-author может использоваться только одновременно с опциями -C, -c или --amend." -#: builtin/commit.c:1191 +#: builtin/commit.c:1190 msgid "Only one of --include/--only/--all/--interactive/--patch can be used." msgstr "Может использоваться только одна из опций --include/--only/--all/--interactive/--patch." -#: builtin/commit.c:1193 +#: builtin/commit.c:1192 msgid "No paths with --include/--only does not make sense." msgstr "Указание путей каталогов с опциями --include/--only не имеет смысла." -#: builtin/commit.c:1195 +#: builtin/commit.c:1194 msgid "Clever... amending the last one with dirty index." msgstr "Умно… отмена последнего с измененным индексом." -#: builtin/commit.c:1197 +#: builtin/commit.c:1196 msgid "Explicit paths specified without -i or -o; assuming --only paths..." msgstr "Пути явно указаны пути без опций -i или -o; предполагаю опцию --only…" -#: builtin/commit.c:1209 builtin/tag.c:728 +#: builtin/commit.c:1208 builtin/tag.c:730 #, c-format msgid "Invalid cleanup mode %s" msgstr "Неправильное значение режима очистки %s" -#: builtin/commit.c:1214 +#: builtin/commit.c:1213 msgid "Paths with -a does not make sense." msgstr "С опцией -a указание пути не имеет смысла." -#: builtin/commit.c:1324 builtin/commit.c:1603 +#: builtin/commit.c:1323 builtin/commit.c:1602 msgid "show status concisely" msgstr "кратко показать статус" -#: builtin/commit.c:1326 builtin/commit.c:1605 +#: builtin/commit.c:1325 builtin/commit.c:1604 msgid "show branch information" msgstr "показать информацию о версии" -#: builtin/commit.c:1328 builtin/commit.c:1607 builtin/push.c:509 +#: builtin/commit.c:1327 builtin/commit.c:1606 builtin/push.c:546 msgid "machine-readable output" msgstr "машиночитаемый вывод" -#: builtin/commit.c:1331 builtin/commit.c:1609 +#: builtin/commit.c:1330 builtin/commit.c:1608 msgid "show status in long format (default)" msgstr "показать статус в длинном формате (по умолчанию)" -#: builtin/commit.c:1334 builtin/commit.c:1612 +#: builtin/commit.c:1333 builtin/commit.c:1611 msgid "terminate entries with NUL" msgstr "завершать записи НУЛЕВЫМ байтом" -#: builtin/commit.c:1336 builtin/commit.c:1615 builtin/fast-export.c:980 -#: builtin/fast-export.c:983 builtin/tag.c:603 +#: builtin/commit.c:1335 builtin/commit.c:1614 builtin/fast-export.c:981 +#: builtin/fast-export.c:984 builtin/tag.c:604 msgid "mode" msgstr "режим" -#: builtin/commit.c:1337 builtin/commit.c:1615 +#: builtin/commit.c:1336 builtin/commit.c:1614 msgid "show untracked files, optional modes: all, normal, no. (Default: all)" msgstr "показать неотслеживаемые файлы, опциональные режимы: all (все), normal (как обычно), no (нет). (По умолчанию: all)" -#: builtin/commit.c:1340 +#: builtin/commit.c:1339 msgid "show ignored files" msgstr "показать игнорируемые файлы" -#: builtin/commit.c:1341 parse-options.h:152 +#: builtin/commit.c:1340 parse-options.h:155 msgid "when" msgstr "когда" -#: builtin/commit.c:1342 +#: builtin/commit.c:1341 msgid "" "ignore changes to submodules, optional when: all, dirty, untracked. " "(Default: all)" msgstr "игнорировать изменения в подмодулях, опционально когда: all (всегда), dirty (измененные), untracked (неотслеживаемые). (По умолчанию: all)" -#: builtin/commit.c:1344 +#: builtin/commit.c:1343 msgid "list untracked files in columns" msgstr "показать неотслеживаемые файлы по столбцам" -#: builtin/commit.c:1430 +#: builtin/commit.c:1429 msgid "couldn't look up newly created commit" msgstr "нельзя запросить новосозданный коммит" -#: builtin/commit.c:1432 +#: builtin/commit.c:1431 msgid "could not parse newly created commit" msgstr "нельзя разобрать новосозданный коммит" -#: builtin/commit.c:1477 +#: builtin/commit.c:1476 msgid "detached HEAD" msgstr "отделенный HEAD" -#: builtin/commit.c:1480 +#: builtin/commit.c:1479 msgid " (root-commit)" msgstr " (корневой коммит)" -#: builtin/commit.c:1573 +#: builtin/commit.c:1572 msgid "suppress summary after successful commit" msgstr "не выводить сводку после успешного коммита" -#: builtin/commit.c:1574 +#: builtin/commit.c:1573 msgid "show diff in commit message template" msgstr "добавить список изменений в шаблон сообщения коммита" -#: builtin/commit.c:1576 +#: builtin/commit.c:1575 msgid "Commit message options" msgstr "Опции сообщения коммита" -#: builtin/commit.c:1577 builtin/tag.c:601 +#: builtin/commit.c:1576 builtin/tag.c:602 msgid "read message from file" msgstr "прочитать сообщение из файла" -#: builtin/commit.c:1578 +#: builtin/commit.c:1577 msgid "author" msgstr "автор" -#: builtin/commit.c:1578 +#: builtin/commit.c:1577 msgid "override author for commit" msgstr "подменить автора коммита" -#: builtin/commit.c:1579 builtin/gc.c:280 +#: builtin/commit.c:1578 builtin/gc.c:268 msgid "date" msgstr "дата" -#: builtin/commit.c:1579 +#: builtin/commit.c:1578 msgid "override date for commit" msgstr "подменить дату коммита" -#: builtin/commit.c:1580 builtin/merge.c:218 builtin/notes.c:391 -#: builtin/notes.c:554 builtin/tag.c:599 +#: builtin/commit.c:1579 builtin/merge.c:218 builtin/notes.c:392 +#: builtin/notes.c:555 builtin/tag.c:600 msgid "message" msgstr "сообщение" -#: builtin/commit.c:1580 +#: builtin/commit.c:1579 msgid "commit message" msgstr "сообщение коммита" -#: builtin/commit.c:1581 +#: builtin/commit.c:1580 msgid "reuse and edit message from specified commit" msgstr "использовать и отредактировать сообщение от указанного коммита" -#: builtin/commit.c:1582 +#: builtin/commit.c:1581 msgid "reuse message from specified commit" msgstr "использовать сообщение указанного коммита" -#: builtin/commit.c:1583 +#: builtin/commit.c:1582 msgid "use autosquash formatted message to fixup specified commit" msgstr "использовать форматированное сообщение автоуплотнения для исправления указанного коммита" -#: builtin/commit.c:1584 +#: builtin/commit.c:1583 msgid "use autosquash formatted message to squash specified commit" msgstr "использовать форматированное сообщение автоуплотнения для уплотнения указанного коммита" -#: builtin/commit.c:1585 +#: builtin/commit.c:1584 msgid "the commit is authored by me now (used with -C/-c/--amend)" msgstr "коммит теперь за моим авторством (с использованием -C/-c/--amend)" -#: builtin/commit.c:1586 builtin/log.c:1191 builtin/revert.c:86 +#: builtin/commit.c:1585 builtin/log.c:1216 builtin/revert.c:86 msgid "add Signed-off-by:" msgstr "добавить Signed-off-by:" -#: builtin/commit.c:1587 +#: builtin/commit.c:1586 msgid "use specified template file" msgstr "использовать указанный файл шаблона" -#: builtin/commit.c:1588 +#: builtin/commit.c:1587 msgid "force edit of commit" msgstr "принудительно редактировать коммит" -#: builtin/commit.c:1589 +#: builtin/commit.c:1588 msgid "default" msgstr "по-умолчанию" -#: builtin/commit.c:1589 builtin/tag.c:604 +#: builtin/commit.c:1588 builtin/tag.c:605 msgid "how to strip spaces and #comments from message" msgstr "как удалять пробелы и #комментарии из сообщения коммита" -#: builtin/commit.c:1590 +#: builtin/commit.c:1589 msgid "include status in commit message template" msgstr "включить статус файлов в шаблон сообщения коммита" -#: builtin/commit.c:1591 builtin/merge.c:225 builtin/revert.c:92 -#: builtin/tag.c:605 -msgid "key-id" -msgstr "key-id" - -#: builtin/commit.c:1592 builtin/merge.c:226 builtin/revert.c:93 +#: builtin/commit.c:1591 builtin/merge.c:226 builtin/pull.c:156 +#: builtin/revert.c:93 msgid "GPG sign commit" msgstr "подписать коммит с помощью GPG" -#: builtin/commit.c:1595 +#: builtin/commit.c:1594 msgid "Commit contents options" msgstr "Опции содержимого коммита" -#: builtin/commit.c:1596 +#: builtin/commit.c:1595 msgid "commit all changed files" msgstr "закоммитить все измененные файлы" -#: builtin/commit.c:1597 +#: builtin/commit.c:1596 msgid "add specified files to index for commit" msgstr "добавить указанные файлы в индекс для коммита" -#: builtin/commit.c:1598 +#: builtin/commit.c:1597 msgid "interactively add files" msgstr "интерактивное добавление файлов" -#: builtin/commit.c:1599 +#: builtin/commit.c:1598 msgid "interactively add changes" msgstr "интерактивное добавление изменений" -#: builtin/commit.c:1600 +#: builtin/commit.c:1599 msgid "commit only specified files" msgstr "закоммитить только указанные файлы" -#: builtin/commit.c:1601 +#: builtin/commit.c:1600 msgid "bypass pre-commit hook" msgstr "пропустить перехватчик перед-коммитом" -#: builtin/commit.c:1602 +#: builtin/commit.c:1601 msgid "show what would be committed" msgstr "показать, что будет закоммичено" -#: builtin/commit.c:1613 +#: builtin/commit.c:1612 msgid "amend previous commit" msgstr "исправить предыдущий коммит" -#: builtin/commit.c:1614 +#: builtin/commit.c:1613 msgid "bypass post-rewrite hook" msgstr "пропустить перехватчик после-перезаписи" -#: builtin/commit.c:1619 +#: builtin/commit.c:1618 msgid "ok to record an empty change" msgstr "разрешить запись пустого коммита" -#: builtin/commit.c:1621 +#: builtin/commit.c:1620 msgid "ok to record a change with an empty message" msgstr "разрешить запись изменений с пустым сообщением" -#: builtin/commit.c:1650 +#: builtin/commit.c:1649 msgid "could not parse HEAD commit" msgstr "не удалось разобрать HEAD коммит" -#: builtin/commit.c:1689 builtin/merge.c:1076 -#, c-format -msgid "could not open '%s' for reading" -msgstr "не удалось открыть «%s» для чтения" - -#: builtin/commit.c:1696 +#: builtin/commit.c:1695 #, c-format msgid "Corrupt MERGE_HEAD file (%s)" msgstr "Файл MERGE_HEAD поврежден (%s)" -#: builtin/commit.c:1703 +#: builtin/commit.c:1702 msgid "could not read MERGE_MODE" msgstr "не удалось прочитать MERGE_MODE" -#: builtin/commit.c:1722 +#: builtin/commit.c:1721 #, c-format msgid "could not read commit message: %s" msgstr "не удалось открыть сообщение коммита: %s" -#: builtin/commit.c:1733 +#: builtin/commit.c:1732 #, c-format msgid "Aborting commit; you did not edit the message.\n" msgstr "Отмена коммита; вы не изменили сообщение.\n" -#: builtin/commit.c:1738 +#: builtin/commit.c:1737 #, c-format msgid "Aborting commit due to empty commit message.\n" msgstr "Отмена коммита из-за пустого сообщения коммита.\n" -#: builtin/commit.c:1753 builtin/merge.c:829 builtin/merge.c:854 -msgid "failed to write commit object" -msgstr "сбой записи объекта коммита" - -#: builtin/commit.c:1786 +#: builtin/commit.c:1785 msgid "" "Repository has been updated, but unable to write\n" "new_index file. Check that disk is not full and quota is\n" @@ -4684,131 +5130,135 @@ msgstr "Репозиторий был обновлен, но не удалось msgid "git config []" msgstr "git config [<опции>]" -#: builtin/config.c:53 +#: builtin/config.c:54 msgid "Config file location" msgstr "Размещение файла конфигурации" -#: builtin/config.c:54 +#: builtin/config.c:55 msgid "use global config file" msgstr "использовать глобальный файл конфигурации" -#: builtin/config.c:55 +#: builtin/config.c:56 msgid "use system config file" msgstr "использовать системный файл конфигурации" -#: builtin/config.c:56 +#: builtin/config.c:57 msgid "use repository config file" msgstr "использовать файл конфигурации репозитория" -#: builtin/config.c:57 +#: builtin/config.c:58 msgid "use given config file" msgstr "использовать указанный файл конфигурации" -#: builtin/config.c:58 +#: builtin/config.c:59 msgid "blob-id" msgstr "идент-двоичн-объекта" -#: builtin/config.c:58 +#: builtin/config.c:59 msgid "read config from given blob object" msgstr "прочитать настройки из указанного двоичного объекта" -#: builtin/config.c:59 +#: builtin/config.c:60 msgid "Action" msgstr "Действие" -#: builtin/config.c:60 +#: builtin/config.c:61 msgid "get value: name [value-regex]" msgstr "получить значение: имя [шаблон-значений]" -#: builtin/config.c:61 +#: builtin/config.c:62 msgid "get all values: key [value-regex]" msgstr "получить все значения: ключ [шаблон-значений]" -#: builtin/config.c:62 +#: builtin/config.c:63 msgid "get values for regexp: name-regex [value-regex]" msgstr "получить значения по шаблону: шаблон-имен [шаблон-значений]" -#: builtin/config.c:63 +#: builtin/config.c:64 msgid "get value specific for the URL: section[.var] URL" msgstr "получить значение, специфичное для URL: раздел[.переменная] URL" -#: builtin/config.c:64 +#: builtin/config.c:65 msgid "replace all matching variables: name value [value_regex]" msgstr "заменить все соответствующие переменные: имя значение [шаблон-значений]" -#: builtin/config.c:65 +#: builtin/config.c:66 msgid "add a new variable: name value" msgstr "добавить новую переменную: имя значение" -#: builtin/config.c:66 +#: builtin/config.c:67 msgid "remove a variable: name [value-regex]" msgstr "удалить переменную: имя [шаблон-значений]" -#: builtin/config.c:67 +#: builtin/config.c:68 msgid "remove all matches: name [value-regex]" msgstr "удалить все совпадающие: имя [шаблон-значений]" -#: builtin/config.c:68 +#: builtin/config.c:69 msgid "rename section: old-name new-name" msgstr "переименовать раздел: старое-имя новое-имя" -#: builtin/config.c:69 +#: builtin/config.c:70 msgid "remove a section: name" msgstr "удалить раздел: имя" -#: builtin/config.c:70 +#: builtin/config.c:71 msgid "list all" msgstr "показать весь список" -#: builtin/config.c:71 +#: builtin/config.c:72 msgid "open an editor" msgstr "открыть в редакторе" -#: builtin/config.c:72 +#: builtin/config.c:73 msgid "find the color configured: slot [default]" msgstr "найти настроенный цвет: раздел [по-умолчанию]" -#: builtin/config.c:73 +#: builtin/config.c:74 msgid "find the color setting: slot [stdout-is-tty]" msgstr "проверить, существует ли настроенный цвет: раздел [stdout-есть-tty]" -#: builtin/config.c:74 +#: builtin/config.c:75 msgid "Type" msgstr "Тип" -#: builtin/config.c:75 +#: builtin/config.c:76 msgid "value is \"true\" or \"false\"" msgstr "значение — это «true» (правда) или «false» (ложь)" -#: builtin/config.c:76 +#: builtin/config.c:77 msgid "value is decimal number" msgstr "значение — это десятичное число" -#: builtin/config.c:77 +#: builtin/config.c:78 msgid "value is --bool or --int" msgstr "значение — это --bool или --int" -#: builtin/config.c:78 +#: builtin/config.c:79 msgid "value is a path (file or directory name)" msgstr "значение — это путь (к файлу или каталогу)" -#: builtin/config.c:79 +#: builtin/config.c:80 msgid "Other" msgstr "Другое" -#: builtin/config.c:80 +#: builtin/config.c:81 msgid "terminate values with NUL byte" msgstr "завершать значения НУЛЕВЫМ байтом" -#: builtin/config.c:81 +#: builtin/config.c:82 +msgid "show variable names only" +msgstr "показывать только имена переменных" + +#: builtin/config.c:83 msgid "respect include directives on lookup" msgstr "учитывать директивы include (включения файлов) при запросе" -#: builtin/config.c:316 +#: builtin/config.c:311 msgid "unable to parse default color value" msgstr "не удалось разобрать значение цвета по умолчанию" -#: builtin/config.c:457 +#: builtin/config.c:449 #, c-format msgid "" "# This is Git's per-user configuration file.\n" @@ -4818,7 +5268,7 @@ msgid "" "#\temail = %s\n" msgstr "# Это файл конфигурации пользователя Git.\n[user]\n# Пожалуйста, адаптируйте и раскомментируйте следующие строки:\n#\tuser = %s\n#\temail = %s\n" -#: builtin/config.c:587 +#: builtin/config.c:583 #, c-format msgid "cannot create configuration file %s" msgstr "не удалось создать файл конфигурации %s" @@ -4854,7 +5304,7 @@ msgstr "аннотированная метка %s не содержит вст msgid "tag '%s' is really '%s' here" msgstr "метка «%s» уже здесь «%s»" -#: builtin/describe.c:250 builtin/log.c:452 +#: builtin/describe.c:250 builtin/log.c:459 #, c-format msgid "Not a valid object name %s" msgstr "Недействительное имя объекта %s" @@ -4994,502 +5444,491 @@ msgstr "передано больше двух двоичных объектов msgid "unhandled object '%s' given." msgstr "передан необработанный объект «%s»." -#: builtin/fast-export.c:24 +#: builtin/fast-export.c:25 msgid "git fast-export [rev-list-opts]" msgstr "git fast-export [опции-rev-list]" -#: builtin/fast-export.c:979 +#: builtin/fast-export.c:980 msgid "show progress after objects" msgstr "показать прогресс после объектов" -#: builtin/fast-export.c:981 +#: builtin/fast-export.c:982 msgid "select handling of signed tags" msgstr "выбор обработки подписанных меток" -#: builtin/fast-export.c:984 +#: builtin/fast-export.c:985 msgid "select handling of tags that tag filtered objects" msgstr "выбор обработки меток, которыми помечены отфильтрованные объекты" -#: builtin/fast-export.c:987 +#: builtin/fast-export.c:988 msgid "Dump marks to this file" msgstr "Записать пометки в этот файл" -#: builtin/fast-export.c:989 +#: builtin/fast-export.c:990 msgid "Import marks from this file" msgstr "Импортировать пометки из этого файла" -#: builtin/fast-export.c:991 +#: builtin/fast-export.c:992 msgid "Fake a tagger when tags lack one" msgstr "Подделать автора метки, если у метки он отсутствует" -#: builtin/fast-export.c:993 +#: builtin/fast-export.c:994 msgid "Output full tree for each commit" msgstr "Вывести полное дерево для каждого коммита" -#: builtin/fast-export.c:995 +#: builtin/fast-export.c:996 msgid "Use the done feature to terminate the stream" msgstr "Использовать пометку завершения в конце потока" -#: builtin/fast-export.c:996 +#: builtin/fast-export.c:997 msgid "Skip output of blob data" msgstr "Пропустить вывод данных двоичных объектов" -#: builtin/fast-export.c:997 +#: builtin/fast-export.c:998 msgid "refspec" msgstr "спецификация ссылки" -#: builtin/fast-export.c:998 +#: builtin/fast-export.c:999 msgid "Apply refspec to exported refs" msgstr "Применить спецификацию ссылки к экспортируемым ссылкам" -#: builtin/fast-export.c:999 +#: builtin/fast-export.c:1000 msgid "anonymize output" msgstr "сделать вывод анонимным" -#: builtin/fetch.c:19 +#: builtin/fetch.c:20 msgid "git fetch [] [ [...]]" msgstr "git fetch [<опции>] [<репозиторий> [<спецификация-ссылки>…]]" -#: builtin/fetch.c:20 +#: builtin/fetch.c:21 msgid "git fetch [] " msgstr "git fetch [<опции>] <группа>" -#: builtin/fetch.c:21 +#: builtin/fetch.c:22 msgid "git fetch --multiple [] [( | )...]" msgstr "git fetch --multiple [<опции>] [(<репозиторий> | <группа>)…]" -#: builtin/fetch.c:22 +#: builtin/fetch.c:23 msgid "git fetch --all []" msgstr "git fetch --all [<опции>]" -#: builtin/fetch.c:89 +#: builtin/fetch.c:90 builtin/pull.c:162 msgid "fetch from all remotes" msgstr "извлечь со всех внешних репозиториев" -#: builtin/fetch.c:91 +#: builtin/fetch.c:92 builtin/pull.c:165 msgid "append to .git/FETCH_HEAD instead of overwriting" msgstr "дописать к .git/FETCH_HEAD вместо перезаписи" -#: builtin/fetch.c:93 +#: builtin/fetch.c:94 builtin/pull.c:168 msgid "path to upload pack on remote end" msgstr "путь к программе упаковки пакета на машине с внешним репозиторием" -#: builtin/fetch.c:94 +#: builtin/fetch.c:95 builtin/pull.c:170 msgid "force overwrite of local branch" msgstr "принудительная перезапись локальной ветки" -#: builtin/fetch.c:96 +#: builtin/fetch.c:97 msgid "fetch from multiple remotes" msgstr "извлечь с нескольких внешних репозиториев" -#: builtin/fetch.c:98 +#: builtin/fetch.c:99 builtin/pull.c:172 msgid "fetch all tags and associated objects" msgstr "извлечь все метки и связанные объекты" -#: builtin/fetch.c:100 +#: builtin/fetch.c:101 msgid "do not fetch all tags (--no-tags)" msgstr "не извлекать все метки (--no-tags)" -#: builtin/fetch.c:102 +#: builtin/fetch.c:103 builtin/pull.c:175 msgid "prune remote-tracking branches no longer on remote" msgstr "почистить отслеживаемые внешние ветки, которых уже нет на внешнем репозитории" -#: builtin/fetch.c:103 +#: builtin/fetch.c:104 builtin/pull.c:178 msgid "on-demand" msgstr "по требованию" -#: builtin/fetch.c:104 +#: builtin/fetch.c:105 builtin/pull.c:179 msgid "control recursive fetching of submodules" msgstr "управление рекурсивным извлечением подмодулей" -#: builtin/fetch.c:108 +#: builtin/fetch.c:109 builtin/pull.c:184 msgid "keep downloaded pack" msgstr "оставить загруженный пакет данных" -#: builtin/fetch.c:110 +#: builtin/fetch.c:111 msgid "allow updating of HEAD ref" msgstr "разрешить обновление ссылки HEAD" -#: builtin/fetch.c:113 +#: builtin/fetch.c:114 builtin/pull.c:187 msgid "deepen history of shallow clone" msgstr "глубокая история частичного клона" -#: builtin/fetch.c:115 +#: builtin/fetch.c:116 builtin/pull.c:190 msgid "convert to a complete repository" msgstr "преобразовать в полный репозиторий" -#: builtin/fetch.c:117 builtin/log.c:1208 +#: builtin/fetch.c:118 builtin/log.c:1233 msgid "dir" msgstr "каталог" -#: builtin/fetch.c:118 +#: builtin/fetch.c:119 msgid "prepend this to submodule path output" msgstr "присоединять это спереди к выводу путей подмодуля" -#: builtin/fetch.c:121 +#: builtin/fetch.c:122 msgid "default mode for recursion" msgstr "режим по умолчанию для рекурсии" -#: builtin/fetch.c:123 +#: builtin/fetch.c:124 builtin/pull.c:193 msgid "accept refs that update .git/shallow" msgstr "принимать ссылки, которые обновляют .git/shallow" -#: builtin/fetch.c:124 +#: builtin/fetch.c:125 builtin/pull.c:195 msgid "refmap" msgstr "соответствие-ссылок" -#: builtin/fetch.c:125 +#: builtin/fetch.c:126 builtin/pull.c:196 msgid "specify fetch refmap" msgstr "указать соответствие ссылок при извлечении" -#: builtin/fetch.c:377 +#: builtin/fetch.c:378 msgid "Couldn't find remote ref HEAD" msgstr "Не удалось найти ссылку HEAD на внешнем репозитории" -#: builtin/fetch.c:457 +#: builtin/fetch.c:458 #, c-format msgid "object %s not found" msgstr "объект %s не найден" -#: builtin/fetch.c:462 +#: builtin/fetch.c:463 msgid "[up to date]" msgstr "[актуально]" -#: builtin/fetch.c:476 +#: builtin/fetch.c:477 #, c-format msgid "! %-*s %-*s -> %s (can't fetch in current branch)" msgstr "! %-*s %-*s → %s (не удалось извлечь в текущую ветку)" -#: builtin/fetch.c:477 builtin/fetch.c:563 +#: builtin/fetch.c:478 builtin/fetch.c:564 msgid "[rejected]" msgstr "[отклонено]" -#: builtin/fetch.c:488 +#: builtin/fetch.c:489 msgid "[tag update]" msgstr "[обновление метки]" -#: builtin/fetch.c:490 builtin/fetch.c:525 builtin/fetch.c:543 +#: builtin/fetch.c:491 builtin/fetch.c:526 builtin/fetch.c:544 msgid " (unable to update local ref)" msgstr " (не удалось обновить локальную ссылку)" -#: builtin/fetch.c:508 +#: builtin/fetch.c:509 msgid "[new tag]" msgstr "[новая метка]" -#: builtin/fetch.c:511 +#: builtin/fetch.c:512 msgid "[new branch]" msgstr "[новая ветка]" -#: builtin/fetch.c:514 +#: builtin/fetch.c:515 msgid "[new ref]" msgstr "[новая ссылка]" -#: builtin/fetch.c:559 +#: builtin/fetch.c:560 msgid "unable to update local ref" msgstr "не удалось обновить локальную ссылку" -#: builtin/fetch.c:559 +#: builtin/fetch.c:560 msgid "forced update" msgstr "принудительное обновление" -#: builtin/fetch.c:565 +#: builtin/fetch.c:566 msgid "(non-fast-forward)" msgstr "(без перемотки вперед)" -#: builtin/fetch.c:599 builtin/fetch.c:832 +#: builtin/fetch.c:600 builtin/fetch.c:842 #, c-format msgid "cannot open %s: %s\n" msgstr "не удалось открыть %s: %s\n" -#: builtin/fetch.c:608 +#: builtin/fetch.c:609 #, c-format msgid "%s did not send all necessary objects\n" msgstr "%s не отправил все необходимые объекты\n" -#: builtin/fetch.c:626 +#: builtin/fetch.c:627 #, c-format msgid "reject %s because shallow roots are not allowed to be updated" msgstr "%s отклонено из-за того, что частичные корни не разрешено обновлять" -#: builtin/fetch.c:714 builtin/fetch.c:797 +#: builtin/fetch.c:715 builtin/fetch.c:807 #, c-format msgid "From %.*s\n" msgstr "Из %.*s\n" -#: builtin/fetch.c:725 +#: builtin/fetch.c:726 #, c-format msgid "" "some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting branches" msgstr "не удалось обновить некоторые локальные ссылки; попробуйте запустить «git remote prune %s», чтобы почистить старые, конфликтующие ветки" -#: builtin/fetch.c:777 +#: builtin/fetch.c:778 #, c-format msgid " (%s will become dangling)" msgstr " (%s будет висящей веткой)" -#: builtin/fetch.c:778 +#: builtin/fetch.c:779 #, c-format msgid " (%s has become dangling)" msgstr " (%s стала висящей веткой)" -#: builtin/fetch.c:802 +#: builtin/fetch.c:811 msgid "[deleted]" msgstr "[удалено]" -#: builtin/fetch.c:803 builtin/remote.c:1057 +#: builtin/fetch.c:812 builtin/remote.c:1034 msgid "(none)" msgstr "(нет)" -#: builtin/fetch.c:822 +#: builtin/fetch.c:832 #, c-format msgid "Refusing to fetch into current branch %s of non-bare repository" msgstr "Отказ получения в текущую ветку %s не голого репозитория" -#: builtin/fetch.c:841 +#: builtin/fetch.c:851 #, c-format msgid "Option \"%s\" value \"%s\" is not valid for %s" msgstr "Неправильное значение «%2$s» для параметра «%1$s» для %3$s" -#: builtin/fetch.c:844 +#: builtin/fetch.c:854 #, c-format msgid "Option \"%s\" is ignored for %s\n" msgstr "Параметр «%s» игнорируется для %s\n" -#: builtin/fetch.c:900 +#: builtin/fetch.c:910 #, c-format msgid "Don't know how to fetch from %s" msgstr "Не знаю как извлечь с %s" -#: builtin/fetch.c:1063 +#: builtin/fetch.c:1071 #, c-format msgid "Fetching %s\n" msgstr "Извлечение из %s\n" -#: builtin/fetch.c:1065 builtin/remote.c:90 +#: builtin/fetch.c:1073 builtin/remote.c:90 #, c-format msgid "Could not fetch %s" msgstr "Не удалось извлечь %s" -#: builtin/fetch.c:1083 +#: builtin/fetch.c:1091 msgid "" "No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched." msgstr "Не указан внешний репозиторий. Укажите URL или имя внешнего репозитория из которого должны извлекаться новые редакции." -#: builtin/fetch.c:1106 +#: builtin/fetch.c:1114 msgid "You need to specify a tag name." msgstr "Вам нужно указать имя метки." -#: builtin/fetch.c:1148 +#: builtin/fetch.c:1156 msgid "--depth and --unshallow cannot be used together" msgstr "нельзя использовать одновременно --depth и --unshallow" -#: builtin/fetch.c:1150 +#: builtin/fetch.c:1158 msgid "--unshallow on a complete repository does not make sense" msgstr "--unshallow не имеет смысла на полном репозитории" -#: builtin/fetch.c:1173 +#: builtin/fetch.c:1181 msgid "fetch --all does not take a repository argument" msgstr "fetch --all не принимает имя репозитория как аргумент" -#: builtin/fetch.c:1175 +#: builtin/fetch.c:1183 msgid "fetch --all does not make sense with refspecs" msgstr "fetch --all не имеет смысла при указании спецификаций ссылок" -#: builtin/fetch.c:1186 +#: builtin/fetch.c:1194 #, c-format msgid "No such remote or remote group: %s" msgstr "Нет такого внешнего репозитория или группы: %s" -#: builtin/fetch.c:1194 +#: builtin/fetch.c:1202 msgid "Fetching a group and specifying refspecs does not make sense" msgstr "Получение группы и указание спецификаций ссылок не имеет смысла" -#: builtin/fmt-merge-msg.c:13 +#: builtin/fmt-merge-msg.c:14 msgid "" "git fmt-merge-msg [-m ] [--log[=] | --no-log] [--file ]" msgstr "git fmt-merge-msg [-m <сообщение>] [--log[=] | --no-log] [--file <файл>]" -#: builtin/fmt-merge-msg.c:668 builtin/fmt-merge-msg.c:671 builtin/grep.c:698 -#: builtin/merge.c:198 builtin/repack.c:178 builtin/repack.c:182 -#: builtin/show-branch.c:664 builtin/show-ref.c:180 builtin/tag.c:590 -#: parse-options.h:131 parse-options.h:238 -msgid "n" -msgstr "n" - -#: builtin/fmt-merge-msg.c:669 +#: builtin/fmt-merge-msg.c:670 msgid "populate log with at most entries from shortlog" msgstr "отправить в журнал записей из короткого журнала" -#: builtin/fmt-merge-msg.c:672 +#: builtin/fmt-merge-msg.c:673 msgid "alias for --log (deprecated)" msgstr "сокращение для --log (устаревшее)" -#: builtin/fmt-merge-msg.c:675 +#: builtin/fmt-merge-msg.c:676 msgid "text" msgstr "текст" -#: builtin/fmt-merge-msg.c:676 +#: builtin/fmt-merge-msg.c:677 msgid "use as start of message" msgstr "использовать <текст> как начальное сообщение" -#: builtin/fmt-merge-msg.c:677 +#: builtin/fmt-merge-msg.c:678 msgid "file to read from" msgstr "файл для чтения" -#: builtin/for-each-ref.c:687 -msgid "unable to parse format" -msgstr "не удалось разобрать формат" - -#: builtin/for-each-ref.c:1083 +#: builtin/for-each-ref.c:9 msgid "git for-each-ref [] []" msgstr "git for-each-ref [<опции>] [<шаблон>]" -#: builtin/for-each-ref.c:1098 +#: builtin/for-each-ref.c:24 msgid "quote placeholders suitably for shells" msgstr "выводить указатели места заполнения в подходящем формате для командного процессора" -#: builtin/for-each-ref.c:1100 +#: builtin/for-each-ref.c:26 msgid "quote placeholders suitably for perl" msgstr "выводить указатели места заполнения в подходящем формате для perl" -#: builtin/for-each-ref.c:1102 +#: builtin/for-each-ref.c:28 msgid "quote placeholders suitably for python" msgstr "выводить указатели места заполнения в подходящем формате для python" -#: builtin/for-each-ref.c:1104 +#: builtin/for-each-ref.c:30 msgid "quote placeholders suitably for Tcl" msgstr "выводить указатели места заполнения в подходящем формате для Tcl" -#: builtin/for-each-ref.c:1107 +#: builtin/for-each-ref.c:33 msgid "show only matched refs" msgstr "показать только совпадающих ссылок" -#: builtin/for-each-ref.c:1108 builtin/replace.c:438 -msgid "format" -msgstr "формат" - -#: builtin/for-each-ref.c:1108 +#: builtin/for-each-ref.c:34 msgid "format to use for the output" msgstr "использовать формат для вывода" -#: builtin/for-each-ref.c:1109 +#: builtin/for-each-ref.c:35 msgid "key" msgstr "ключ" -#: builtin/for-each-ref.c:1110 +#: builtin/for-each-ref.c:36 msgid "field name to sort on" msgstr "имя поля, по которому выполнить сортировку" -#: builtin/fsck.c:147 builtin/prune.c:137 +#: builtin/fsck.c:163 builtin/prune.c:137 msgid "Checking connectivity" msgstr "Проверка соединения" -#: builtin/fsck.c:548 +#: builtin/fsck.c:568 msgid "Checking object directories" msgstr "Проверка каталогов объектов" -#: builtin/fsck.c:611 +#: builtin/fsck.c:631 msgid "git fsck [] [...]" msgstr "git fsck [<опции>] [<объект>…]" -#: builtin/fsck.c:617 +#: builtin/fsck.c:637 msgid "show unreachable objects" msgstr "показать недоступные объекты" -#: builtin/fsck.c:618 +#: builtin/fsck.c:638 msgid "show dangling objects" msgstr "показать объекты, на которые нет ссылок" -#: builtin/fsck.c:619 +#: builtin/fsck.c:639 msgid "report tags" msgstr "вывести отчет по меткам" -#: builtin/fsck.c:620 +#: builtin/fsck.c:640 msgid "report root nodes" msgstr "вывести отчет по корневым узлам" -#: builtin/fsck.c:621 +#: builtin/fsck.c:641 msgid "make index objects head nodes" msgstr "воспринимать объекты в индексе как корневые узлы" -#: builtin/fsck.c:622 +#: builtin/fsck.c:642 msgid "make reflogs head nodes (default)" msgstr "создать корневые узлы журналов ссылок (по умолчанию)" -#: builtin/fsck.c:623 +#: builtin/fsck.c:643 msgid "also consider packs and alternate objects" msgstr "также проверять пакеты и альтернативные объекты" -#: builtin/fsck.c:624 +#: builtin/fsck.c:644 +msgid "check only connectivity" +msgstr "только проверить соединение" + +#: builtin/fsck.c:645 msgid "enable more strict checking" msgstr "использовать более строгую проверку" -#: builtin/fsck.c:626 +#: builtin/fsck.c:647 msgid "write dangling objects in .git/lost-found" msgstr "записать объекты на которые нет ссылок в .git/lost-found" -#: builtin/fsck.c:627 builtin/prune.c:107 +#: builtin/fsck.c:648 builtin/prune.c:107 msgid "show progress" msgstr "показать прогресс выполнения" -#: builtin/fsck.c:677 +#: builtin/fsck.c:707 msgid "Checking objects" msgstr "Проверка объектов" -#: builtin/gc.c:24 +#: builtin/gc.c:25 msgid "git gc []" msgstr "git gc [<опции>]" -#: builtin/gc.c:67 +#: builtin/gc.c:55 #, c-format msgid "Invalid %s: '%s'" msgstr "Недействительный %s: «%s»" -#: builtin/gc.c:112 +#: builtin/gc.c:100 #, c-format msgid "insanely long object directory %.*s" msgstr "слишком длинный путь к каталогу объекта %.*s" -#: builtin/gc.c:281 +#: builtin/gc.c:269 msgid "prune unreferenced objects" msgstr "почистить объекты, на которые нет ссылок" -#: builtin/gc.c:283 +#: builtin/gc.c:271 msgid "be more thorough (increased runtime)" msgstr "проверять более внимательно (занимает больше времени)" -#: builtin/gc.c:284 +#: builtin/gc.c:272 msgid "enable auto-gc mode" msgstr "включить режим auto-gc" -#: builtin/gc.c:285 +#: builtin/gc.c:273 msgid "force running gc even if there may be another gc running" msgstr "принудительно запустить gc, даже есть другая копия gc уже запущена" -#: builtin/gc.c:327 +#: builtin/gc.c:315 #, c-format msgid "Auto packing the repository in background for optimum performance.\n" msgstr "Автоматическая упаковка репозитория в фоне, для оптимальной производительности.\n" -#: builtin/gc.c:329 +#: builtin/gc.c:317 #, c-format msgid "Auto packing the repository for optimum performance.\n" msgstr "Автоматическая упаковка репозитория, для оптимальной производительности.\n" -#: builtin/gc.c:330 +#: builtin/gc.c:318 #, c-format msgid "See \"git help gc\" for manual housekeeping.\n" msgstr "Смотрите «git help gc» руководства по ручной очистке.\n" -#: builtin/gc.c:348 +#: builtin/gc.c:336 #, c-format msgid "" "gc is already running on machine '%s' pid % (use --force if not)" msgstr "gc уже запущен на этом компьютере «%s» pid % (если нет, используйте --force)" -#: builtin/gc.c:376 +#: builtin/gc.c:364 msgid "" "There are too many unreachable loose objects; run 'git prune' to remove " "them." @@ -5730,7 +6169,7 @@ msgstr "git hash-object [-t <тип>] [-w] [--path=<файл> | --no-filters] [- msgid "git hash-object --stdin-paths < " msgstr "git hash-object --stdin-paths < <список-путей>" -#: builtin/hash-object.c:92 builtin/tag.c:612 +#: builtin/hash-object.c:92 builtin/tag.c:614 msgid "type" msgstr "тип" @@ -5879,27 +6318,27 @@ msgstr "использование: %s%s" msgid "`git %s' is aliased to `%s'" msgstr "«git %s» — это сокращение для «%s»" -#: builtin/index-pack.c:151 +#: builtin/index-pack.c:152 #, c-format msgid "unable to open %s" msgstr "не удалось открыть %s" -#: builtin/index-pack.c:201 +#: builtin/index-pack.c:202 #, c-format msgid "object type mismatch at %s" msgstr "несоответствие типа объекта на %s" -#: builtin/index-pack.c:221 +#: builtin/index-pack.c:222 #, c-format msgid "did not receive expected object %s" msgstr "ожидаемый объект не получен на %s" -#: builtin/index-pack.c:224 +#: builtin/index-pack.c:225 #, c-format msgid "object %s: expected type %s, found %s" msgstr "объект %s: ожидаемый тип %s, получен %s" -#: builtin/index-pack.c:266 +#: builtin/index-pack.c:267 #, c-format msgid "cannot fill %d byte" msgid_plural "cannot fill %d bytes" @@ -5908,69 +6347,69 @@ msgstr[1] "не удалось заполнить %d байта" msgstr[2] "не удалось заполнить %d байтов" msgstr[3] "не удалось заполнить %d байтов" -#: builtin/index-pack.c:276 +#: builtin/index-pack.c:277 msgid "early EOF" msgstr "неожиданный конец файла" -#: builtin/index-pack.c:277 +#: builtin/index-pack.c:278 msgid "read error on input" msgstr "ошибка чтения ввода" -#: builtin/index-pack.c:289 +#: builtin/index-pack.c:290 msgid "used more bytes than were available" msgstr "использовано больше байт, чем было доступно" -#: builtin/index-pack.c:296 +#: builtin/index-pack.c:297 msgid "pack too large for current definition of off_t" msgstr "пакет слишком большой для текущего определения off_t" -#: builtin/index-pack.c:312 +#: builtin/index-pack.c:313 #, c-format msgid "unable to create '%s'" msgstr "не удалось создать «%s»" -#: builtin/index-pack.c:317 +#: builtin/index-pack.c:318 #, c-format msgid "cannot open packfile '%s'" msgstr "не удалось открыть файл пакета «%s»" -#: builtin/index-pack.c:331 +#: builtin/index-pack.c:332 msgid "pack signature mismatch" msgstr "несоответствие подписи пакета" -#: builtin/index-pack.c:333 +#: builtin/index-pack.c:334 #, c-format msgid "pack version % unsupported" msgstr "версия пакета % не поддерживается" -#: builtin/index-pack.c:351 +#: builtin/index-pack.c:352 #, c-format msgid "pack has bad object at offset %lu: %s" msgstr "в пакете содержится поврежденный объект по смещению %lu: %s" -#: builtin/index-pack.c:472 +#: builtin/index-pack.c:473 #, c-format msgid "inflate returned %d" msgstr "программа сжатия вернула %d" -#: builtin/index-pack.c:521 +#: builtin/index-pack.c:522 msgid "offset value overflow for delta base object" msgstr "переполнение значения смещения у базового объекта дельты" -#: builtin/index-pack.c:529 +#: builtin/index-pack.c:530 msgid "delta base offset is out of bound" msgstr "смещение базовой дельты вышло за допустимые пределы" -#: builtin/index-pack.c:537 +#: builtin/index-pack.c:538 #, c-format msgid "unknown object type %d" msgstr "неизвестный тип объекта %d" -#: builtin/index-pack.c:568 +#: builtin/index-pack.c:569 msgid "cannot pread pack file" msgstr "не удалось выполнить pread для файла пакета" -#: builtin/index-pack.c:570 +#: builtin/index-pack.c:571 #, c-format msgid "premature end of pack file, %lu byte missing" msgid_plural "premature end of pack file, %lu bytes missing" @@ -5979,33 +6418,33 @@ msgstr[1] "преждевременное окончание файла паке msgstr[2] "преждевременное окончание файла пакета, %lu байтов отсутствует" msgstr[3] "преждевременное окончание файла пакета, %lu байтов отсутствует" -#: builtin/index-pack.c:596 +#: builtin/index-pack.c:597 msgid "serious inflate inconsistency" msgstr "серьезное несоответствие при распаковке" -#: builtin/index-pack.c:742 builtin/index-pack.c:748 builtin/index-pack.c:771 -#: builtin/index-pack.c:805 builtin/index-pack.c:814 +#: builtin/index-pack.c:743 builtin/index-pack.c:749 builtin/index-pack.c:772 +#: builtin/index-pack.c:806 builtin/index-pack.c:815 #, c-format msgid "SHA1 COLLISION FOUND WITH %s !" msgstr "НАЙДЕНА КОЛЛИЗИЯ SHA1 С %s !" -#: builtin/index-pack.c:745 builtin/pack-objects.c:162 +#: builtin/index-pack.c:746 builtin/pack-objects.c:162 #: builtin/pack-objects.c:254 #, c-format msgid "unable to read %s" msgstr "не удалось прочитать %s" -#: builtin/index-pack.c:811 +#: builtin/index-pack.c:812 #, c-format msgid "cannot read existing object %s" msgstr "не удалось прочитать существующий объект %s" -#: builtin/index-pack.c:825 +#: builtin/index-pack.c:826 #, c-format msgid "invalid blob object %s" msgstr "неправильный файл двоичного объекта %s" -#: builtin/index-pack.c:839 +#: builtin/index-pack.c:840 #, c-format msgid "invalid %s" msgstr "неправильный %s" @@ -6121,7 +6560,7 @@ msgstr "плохой pack.indexversion=%" msgid "invalid number of threads specified (%d)" msgstr "указано неправильное количество потоков (%d)" -#: builtin/index-pack.c:1479 builtin/index-pack.c:1658 +#: builtin/index-pack.c:1479 builtin/index-pack.c:1663 #, c-format msgid "no threads support, ignoring %s" msgstr "нет поддержки потоков, игнорирование %s" @@ -6154,110 +6593,110 @@ msgstr[1] "длина цепочки = %d: %lu объекта" msgstr[2] "длина цепочки = %d: %lu объектов" msgstr[3] "длина цепочки = %d: %lu объектов" -#: builtin/index-pack.c:1622 +#: builtin/index-pack.c:1623 msgid "Cannot come back to cwd" msgstr "Не удалось вернуться в текущий рабочий каталог" -#: builtin/index-pack.c:1670 builtin/index-pack.c:1673 -#: builtin/index-pack.c:1685 builtin/index-pack.c:1689 +#: builtin/index-pack.c:1675 builtin/index-pack.c:1678 +#: builtin/index-pack.c:1690 builtin/index-pack.c:1694 #, c-format msgid "bad %s" msgstr "плохой %s" -#: builtin/index-pack.c:1703 +#: builtin/index-pack.c:1708 msgid "--fix-thin cannot be used without --stdin" msgstr "--fix-thin нельзя использовать без --stdin" -#: builtin/index-pack.c:1707 builtin/index-pack.c:1716 +#: builtin/index-pack.c:1712 builtin/index-pack.c:1721 #, c-format msgid "packfile name '%s' does not end with '.pack'" msgstr "имя пакета «%s» не оканчивается на «.pack»" -#: builtin/index-pack.c:1724 +#: builtin/index-pack.c:1729 msgid "--verify with no packfile name given" msgstr "--verify без указания имени файла пакета" -#: builtin/init-db.c:35 +#: builtin/init-db.c:36 #, c-format msgid "Could not make %s writable by group" msgstr "Не удалось предоставить доступ к %s на запись" -#: builtin/init-db.c:62 +#: builtin/init-db.c:63 #, c-format msgid "insanely long template name %s" msgstr "слишком длинное имя шаблона %s" -#: builtin/init-db.c:67 +#: builtin/init-db.c:68 #, c-format msgid "cannot stat '%s'" msgstr "не удалось выполнить stat для «%s»" -#: builtin/init-db.c:73 +#: builtin/init-db.c:74 #, c-format msgid "cannot stat template '%s'" msgstr "не удалось выполнить stat для шаблона «%s»" -#: builtin/init-db.c:80 +#: builtin/init-db.c:81 #, c-format msgid "cannot opendir '%s'" msgstr "не удалось выполнить opendir для «%s»" -#: builtin/init-db.c:97 +#: builtin/init-db.c:98 #, c-format msgid "cannot readlink '%s'" msgstr "не удалось выполнить readlink для «%s»" -#: builtin/init-db.c:99 +#: builtin/init-db.c:100 #, c-format msgid "insanely long symlink %s" msgstr "слишком длинная символьная ссылка %s" -#: builtin/init-db.c:102 +#: builtin/init-db.c:103 #, c-format msgid "cannot symlink '%s' '%s'" msgstr "не удалось создать символьную ссылку «%s» на «%s»" -#: builtin/init-db.c:106 +#: builtin/init-db.c:107 #, c-format msgid "cannot copy '%s' to '%s'" msgstr "не удалось скопировать файл «%s» в «%s»" -#: builtin/init-db.c:110 +#: builtin/init-db.c:111 #, c-format msgid "ignoring template %s" msgstr "игнорирование шаблона %s" -#: builtin/init-db.c:136 +#: builtin/init-db.c:137 #, c-format msgid "insanely long template path %s" msgstr "слишком длинный путь шаблона %s" -#: builtin/init-db.c:144 +#: builtin/init-db.c:145 #, c-format msgid "templates not found %s" msgstr "шаблоны не найдены %s" -#: builtin/init-db.c:157 +#: builtin/init-db.c:158 #, c-format msgid "not copying templates of a wrong format version %d from '%s'" msgstr "не копирую шаблоны в неправильной версии формата %d из «%s»" -#: builtin/init-db.c:211 +#: builtin/init-db.c:212 #, c-format msgid "insane git directory %s" msgstr "слишком длинный путь к каталогу git %s" -#: builtin/init-db.c:343 builtin/init-db.c:346 +#: builtin/init-db.c:344 builtin/init-db.c:347 #, c-format msgid "%s already exists" msgstr "%s уже существует" -#: builtin/init-db.c:374 +#: builtin/init-db.c:375 #, c-format msgid "unable to handle file type %d" msgstr "не удается обработать файл типа %d" -#: builtin/init-db.c:377 +#: builtin/init-db.c:378 #, c-format msgid "unable to move %s to %s" msgstr "не удается переместить файл %s в %s" @@ -6265,59 +6704,55 @@ msgstr "не удается переместить файл %s в %s" #. TRANSLATORS: The first '%s' is either "Reinitialized #. existing" or "Initialized empty", the second " shared" or #. "", and the last '%s%s' is the verbatim directory name. -#: builtin/init-db.c:433 +#: builtin/init-db.c:434 #, c-format msgid "%s%s Git repository in %s%s\n" msgstr "%s%s репозиторий Git в %s%s\n" -#: builtin/init-db.c:434 +#: builtin/init-db.c:435 msgid "Reinitialized existing" msgstr "Реинициализация существующего" -#: builtin/init-db.c:434 +#: builtin/init-db.c:435 msgid "Initialized empty" msgstr "Инициализирован пустой" -#: builtin/init-db.c:435 +#: builtin/init-db.c:436 msgid " shared" msgstr " общий" -#: builtin/init-db.c:482 +#: builtin/init-db.c:483 msgid "" "git init [-q | --quiet] [--bare] [--template=] " "[--shared[=]] []" msgstr "git init [-q | --quiet] [--bare] [--template=<каталог-шаблонов>] [--shared[=<права-доступа>]] [<каталог>]" -#: builtin/init-db.c:505 +#: builtin/init-db.c:506 msgid "permissions" msgstr "права-доступа" -#: builtin/init-db.c:506 +#: builtin/init-db.c:507 msgid "specify that the git repository is to be shared amongst several users" msgstr "укажите, если репозиторий git будет использоваться несколькими пользователями" -#: builtin/init-db.c:508 builtin/prune-packed.c:57 builtin/repack.c:171 -msgid "be quiet" -msgstr "тихий режим" - -#: builtin/init-db.c:540 builtin/init-db.c:545 +#: builtin/init-db.c:541 builtin/init-db.c:546 #, c-format msgid "cannot mkdir %s" msgstr "не удалось выполнить mkdir %s" -#: builtin/init-db.c:549 +#: builtin/init-db.c:550 #, c-format msgid "cannot chdir to %s" msgstr "не удалось выполнить chdir в %s" -#: builtin/init-db.c:570 +#: builtin/init-db.c:571 #, c-format msgid "" "%s (or --work-tree=) not allowed without specifying %s (or --git-" "dir=)" msgstr "%s (или --work-tree=<каталог>) нельзя использовать без указания %s (или --git-dir=<каталог>)" -#: builtin/init-db.c:598 +#: builtin/init-db.c:599 #, c-format msgid "Cannot access work tree '%s'" msgstr "Не удалось получить доступ к рабочему каталогу «%s»" @@ -6340,284 +6775,279 @@ msgstr "завершитель" msgid "trailer(s) to add" msgstr "завершители для добавления" -#: builtin/log.c:41 +#: builtin/log.c:43 msgid "git log [] [] [[--] ...]" msgstr "git log [<опции>] [<диапазон-редакций>] [[--] <путь>…]" -#: builtin/log.c:42 +#: builtin/log.c:44 msgid "git show [] ..." msgstr "git show [<опции>] <объект>…" -#: builtin/log.c:81 +#: builtin/log.c:83 #, c-format msgid "invalid --decorate option: %s" msgstr "неправильный параметр для --decorate: %s" -#: builtin/log.c:127 +#: builtin/log.c:131 msgid "suppress diff output" msgstr "не выводить различия" -#: builtin/log.c:128 +#: builtin/log.c:132 msgid "show source" msgstr "показать источник" -#: builtin/log.c:129 +#: builtin/log.c:133 msgid "Use mail map file" msgstr "Использовать файл соответствия почтовых адресов" -#: builtin/log.c:130 +#: builtin/log.c:134 msgid "decorate options" msgstr "опции формата вывода ссылок" -#: builtin/log.c:133 +#: builtin/log.c:137 msgid "Process line range n,m in file, counting from 1" msgstr "Обработать диапазон строк n,m из файла, начиная с 1" -#: builtin/log.c:229 +#: builtin/log.c:233 #, c-format msgid "Final output: %d %s\n" msgstr "Финальный вывод: %d %s\n" -#: builtin/log.c:458 +#: builtin/log.c:465 #, c-format msgid "git show %s: bad file" msgstr "git show %s: плохой файл" -#: builtin/log.c:472 builtin/log.c:564 +#: builtin/log.c:479 builtin/log.c:572 #, c-format msgid "Could not read object %s" msgstr "Не удалось прочитать объект %s" -#: builtin/log.c:588 +#: builtin/log.c:596 #, c-format msgid "Unknown type: %d" msgstr "Неизвестный тип объекта: %d" -#: builtin/log.c:689 +#: builtin/log.c:714 msgid "format.headers without value" msgstr "в format.headers не указано значение" -#: builtin/log.c:773 +#: builtin/log.c:798 msgid "name of output directory is too long" msgstr "слишком длинное имя выходного каталога" -#: builtin/log.c:789 +#: builtin/log.c:814 #, c-format msgid "Cannot open patch file %s" msgstr "Ну удалось открыть файл изменений %s" -#: builtin/log.c:803 +#: builtin/log.c:828 msgid "Need exactly one range." msgstr "Нужен только один диапазон." -#: builtin/log.c:813 +#: builtin/log.c:838 msgid "Not a range." msgstr "Не является диапазоном." -#: builtin/log.c:919 +#: builtin/log.c:944 msgid "Cover letter needs email format" msgstr "Сопроводительное письмо должно быть в формате электронной почты" -#: builtin/log.c:998 +#: builtin/log.c:1023 #, c-format msgid "insane in-reply-to: %s" msgstr "ошибка в поле in-reply-to: %s" -#: builtin/log.c:1026 +#: builtin/log.c:1051 msgid "git format-patch [] [ | ]" msgstr "git format-patch [<опции>] [<начиная-с> | <диапазон-редакций>]" -#: builtin/log.c:1071 +#: builtin/log.c:1096 msgid "Two output directories?" msgstr "Два выходных каталога?" -#: builtin/log.c:1186 +#: builtin/log.c:1211 msgid "use [PATCH n/m] even with a single patch" msgstr "выводить [PATCH n/m] даже когда один патч" -#: builtin/log.c:1189 +#: builtin/log.c:1214 msgid "use [PATCH] even with multiple patches" msgstr "выводить [PATCH] даже когда несколько патчей" -#: builtin/log.c:1193 +#: builtin/log.c:1218 msgid "print patches to standard out" msgstr "выводить патчи на стандартный вывод" -#: builtin/log.c:1195 +#: builtin/log.c:1220 msgid "generate a cover letter" msgstr "генерировать сопроводительное письмо" -#: builtin/log.c:1197 +#: builtin/log.c:1222 msgid "use simple number sequence for output file names" msgstr "использовать простую последовательность чисел для имен выходных файлов" -#: builtin/log.c:1198 +#: builtin/log.c:1223 msgid "sfx" msgstr "суффикс" -#: builtin/log.c:1199 +#: builtin/log.c:1224 msgid "use instead of '.patch'" msgstr "использовать суффикс <суффикс> вместо «.patch»" -#: builtin/log.c:1201 +#: builtin/log.c:1226 msgid "start numbering patches at instead of 1" msgstr "начать нумерацию патчей с , а не с 1" -#: builtin/log.c:1203 +#: builtin/log.c:1228 msgid "mark the series as Nth re-roll" msgstr "пометить серию как энную попытку" -#: builtin/log.c:1205 +#: builtin/log.c:1230 msgid "Use [] instead of [PATCH]" msgstr "Использовать [<префикс>] вместо [PATCH]" -#: builtin/log.c:1208 +#: builtin/log.c:1233 msgid "store resulting files in " msgstr "сохранить результирующие файлы в <каталог>" -#: builtin/log.c:1211 +#: builtin/log.c:1236 msgid "don't strip/add [PATCH]" msgstr "не обрезать/добавлять [PATCH]" -#: builtin/log.c:1214 +#: builtin/log.c:1239 msgid "don't output binary diffs" msgstr "не выводить двоичные различия" -#: builtin/log.c:1216 +#: builtin/log.c:1241 msgid "don't include a patch matching a commit upstream" msgstr "не включать патч, если коммит уже есть в вышестоящей ветке" -#: builtin/log.c:1218 +#: builtin/log.c:1243 msgid "show patch format instead of default (patch + stat)" msgstr "выводить в формате патча, а не в стандартном (патч + статистика)" -#: builtin/log.c:1220 +#: builtin/log.c:1245 msgid "Messaging" msgstr "Передача сообщений" -#: builtin/log.c:1221 +#: builtin/log.c:1246 msgid "header" msgstr "заголовок" -#: builtin/log.c:1222 +#: builtin/log.c:1247 msgid "add email header" msgstr "добавить заголовок сообщения" -#: builtin/log.c:1223 builtin/log.c:1225 +#: builtin/log.c:1248 builtin/log.c:1250 msgid "email" msgstr "почта" -#: builtin/log.c:1223 +#: builtin/log.c:1248 msgid "add To: header" msgstr "добавить заголовок To:" -#: builtin/log.c:1225 +#: builtin/log.c:1250 msgid "add Cc: header" msgstr "добавить заголовок Cc:" -#: builtin/log.c:1227 +#: builtin/log.c:1252 msgid "ident" msgstr "идентификатор" -#: builtin/log.c:1228 +#: builtin/log.c:1253 msgid "set From address to (or committer ident if absent)" msgstr "установить адрес отправителя на <идентификатор> (или на идентификатор коммитера, если отсутствует)" -#: builtin/log.c:1230 +#: builtin/log.c:1255 msgid "message-id" msgstr "идентификатор-сообщения" -#: builtin/log.c:1231 +#: builtin/log.c:1256 msgid "make first mail a reply to " msgstr "сделать первое письмо ответом на <идентификатор-сообщения>" -#: builtin/log.c:1232 builtin/log.c:1235 +#: builtin/log.c:1257 builtin/log.c:1260 msgid "boundary" msgstr "вложение" -#: builtin/log.c:1233 +#: builtin/log.c:1258 msgid "attach the patch" msgstr "приложить патч" -#: builtin/log.c:1236 +#: builtin/log.c:1261 msgid "inline the patch" msgstr "включить патч в текст письма" -#: builtin/log.c:1240 +#: builtin/log.c:1265 msgid "enable message threading, styles: shallow, deep" msgstr "включить в письмах иерархичность, стили: shallow (частичную), deep (глубокую)" -#: builtin/log.c:1242 +#: builtin/log.c:1267 msgid "signature" msgstr "подпись" -#: builtin/log.c:1243 +#: builtin/log.c:1268 msgid "add a signature" msgstr "добавить подпись" -#: builtin/log.c:1245 +#: builtin/log.c:1270 msgid "add a signature from a file" msgstr "добавить подпись из файла" -#: builtin/log.c:1246 +#: builtin/log.c:1271 msgid "don't print the patch filenames" msgstr "не выводить имена файлов патчей" -#: builtin/log.c:1320 -#, c-format -msgid "invalid ident line: %s" -msgstr "неправильная строка идентификации: %s" - -#: builtin/log.c:1335 +#: builtin/log.c:1360 msgid "-n and -k are mutually exclusive." msgstr "-n и -k нельзя использовать одновременно" -#: builtin/log.c:1337 +#: builtin/log.c:1362 msgid "--subject-prefix and -k are mutually exclusive." msgstr "--subject-prefix и -k нельзя использовать одновременно." -#: builtin/log.c:1345 +#: builtin/log.c:1370 msgid "--name-only does not make sense" msgstr "--name-only не имеет смысла" -#: builtin/log.c:1347 +#: builtin/log.c:1372 msgid "--name-status does not make sense" msgstr "--name-status не имеет смысла" -#: builtin/log.c:1349 +#: builtin/log.c:1374 msgid "--check does not make sense" msgstr "--check не имеет смысла" -#: builtin/log.c:1372 +#: builtin/log.c:1397 msgid "standard output, or directory, which one?" msgstr "стандартный вывод или каталог?" -#: builtin/log.c:1374 +#: builtin/log.c:1399 #, c-format msgid "Could not create directory '%s'" msgstr "Не удалось создать каталог «%s»" -#: builtin/log.c:1472 +#: builtin/log.c:1496 #, c-format msgid "unable to read signature file '%s'" msgstr "не удалось прочитать файл подписи «%s»" -#: builtin/log.c:1535 +#: builtin/log.c:1559 msgid "Failed to create output files" msgstr "Сбой при создании выходных файлов" -#: builtin/log.c:1583 +#: builtin/log.c:1607 msgid "git cherry [-v] [ [ []]]" msgstr "git cherry [-v] [<вышестоящая-ветка> [<голова> [<ограничение>]]]" -#: builtin/log.c:1637 +#: builtin/log.c:1661 #, c-format msgid "" "Could not find a tracked remote branch, please specify " "manually.\n" msgstr "Не удалось найти отслеживаемую внешнюю ветку, укажите <вышестоящую-ветку> вручную.\n" -#: builtin/log.c:1648 builtin/log.c:1650 builtin/log.c:1662 +#: builtin/log.c:1672 builtin/log.c:1674 builtin/log.c:1686 #, c-format msgid "Unknown commit %s" msgstr "Неизвестный коммит %s" @@ -6781,31 +7211,31 @@ msgstr "Доступные стратегии:" msgid "Available custom strategies are:" msgstr "Доступные пользовательские стратегии:" -#: builtin/merge.c:193 +#: builtin/merge.c:193 builtin/pull.c:119 msgid "do not show a diffstat at the end of the merge" msgstr "не выводить статистику изменений после окончания слияния" -#: builtin/merge.c:196 +#: builtin/merge.c:196 builtin/pull.c:122 msgid "show a diffstat at the end of the merge" msgstr "вывести статистику изменений после окончания слияния" -#: builtin/merge.c:197 +#: builtin/merge.c:197 builtin/pull.c:125 msgid "(synonym to --stat)" msgstr "(синоним для --stat)" -#: builtin/merge.c:199 +#: builtin/merge.c:199 builtin/pull.c:128 msgid "add (at most ) entries from shortlog to merge commit message" msgstr "добавить (максимум ) записей из короткого журнала в сообщение коммита у слияния" -#: builtin/merge.c:202 +#: builtin/merge.c:202 builtin/pull.c:131 msgid "create a single commit instead of doing a merge" msgstr "создать один коммит, вместо выполнения слияния" -#: builtin/merge.c:204 +#: builtin/merge.c:204 builtin/pull.c:134 msgid "perform a commit if the merge succeeds (default)" msgstr "сделать коммит, если слияние прошло успешно (по умолчанию)" -#: builtin/merge.c:206 +#: builtin/merge.c:206 builtin/pull.c:137 msgid "edit message before committing" msgstr "отредактировать сообщение перед выполнением коммита" @@ -6813,7 +7243,7 @@ msgstr "отредактировать сообщение перед выпол msgid "allow fast-forward (default)" msgstr "разрешить перемотку вперед (по умолчанию)" -#: builtin/merge.c:209 +#: builtin/merge.c:209 builtin/pull.c:143 msgid "abort if fast-forward is not possible" msgstr "отменить выполнение слияния, если перемотка вперед не возможна" @@ -6821,19 +7251,20 @@ msgstr "отменить выполнение слияния, если пере msgid "Verify that the named commit has a valid GPG signature" msgstr "Проверить, что указанный коммит имеет верную электронную подпись GPG" -#: builtin/merge.c:214 builtin/notes.c:753 builtin/revert.c:89 +#: builtin/merge.c:214 builtin/notes.c:767 builtin/pull.c:148 +#: builtin/revert.c:89 msgid "strategy" msgstr "стратегия" -#: builtin/merge.c:215 +#: builtin/merge.c:215 builtin/pull.c:149 msgid "merge strategy to use" msgstr "используемая стратегия слияния" -#: builtin/merge.c:216 +#: builtin/merge.c:216 builtin/pull.c:152 msgid "option=value" msgstr "опция=значение" -#: builtin/merge.c:217 +#: builtin/merge.c:217 builtin/pull.c:153 msgid "option for selected merge strategy" msgstr "опции для выбранной стратегии слияния" @@ -6871,6 +7302,12 @@ msgstr " (нечего уплотнять)" msgid "Squash commit -- not updating HEAD\n" msgstr "Уплотнение коммита — не обновляя HEAD\n" +#: builtin/merge.c:344 builtin/merge.c:763 builtin/merge.c:975 +#: builtin/merge.c:988 +#, c-format +msgid "Could not write to '%s'" +msgstr "Не удалось записать в «%s»" + #: builtin/merge.c:372 msgid "Writing SQUASH_MSG" msgstr "Запись SQUASH_MSG" @@ -6894,10 +7331,6 @@ msgstr "«%s» не указывает на коммит" msgid "Bad branch.%s.mergeoptions string: %s" msgstr "Неправильная строка branch.%s.mergeoptions: %s" -#: builtin/merge.c:632 -msgid "git write-tree failed to write a tree" -msgstr "git write-tree не удалось записать дерево" - #: builtin/merge.c:656 msgid "Not handling anything other than two heads merge." msgstr "Не обрабатываю ничего, кроме слияния двух указателей на коммиты." @@ -6983,10 +7416,6 @@ msgid "" "Please, commit your changes before you merge." msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD).\nВыполните коммит ваших изменений, перед слиянием." -#: builtin/merge.c:1227 git-pull.sh:74 -msgid "You have not concluded your merge (MERGE_HEAD exists)." -msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD)." - #: builtin/merge.c:1231 msgid "" "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" @@ -7272,7 +7701,7 @@ msgstr "%s, откуда=%s, куда=%s" msgid "Renaming %s to %s\n" msgstr "Переименование %s в %s\n" -#: builtin/mv.c:256 builtin/remote.c:725 builtin/repack.c:361 +#: builtin/mv.c:256 builtin/remote.c:722 builtin/repack.c:362 #, c-format msgid "renaming '%s' failed" msgstr "сбой при переименовании «%s»" @@ -7317,332 +7746,329 @@ msgstr "разрешить вывод «undefined», если не найден msgid "dereference tags in the input (internal use)" msgstr "разыменовывать введенные метки (для внутреннего использования)" -#: builtin/notes.c:24 +#: builtin/notes.c:25 msgid "git notes [--ref ] [list []]" msgstr "git notes [--ref <ссылка-на-заметку>] [list [<объект>]]" -#: builtin/notes.c:25 +#: builtin/notes.c:26 msgid "" "git notes [--ref ] add [-f] [--allow-empty] [-m | -F " " | (-c | -C) ] []" msgstr "git notes [--ref <ссылка-на-заметку>] add [-f] [--allow-empty] [-m <сообщение> | -F <файл> | (-c | -C) <объект>] [<объект>]" -#: builtin/notes.c:26 +#: builtin/notes.c:27 msgid "git notes [--ref ] copy [-f] " msgstr "git notes [--ref <ссылка-на-заметку>] copy [-f] <из-объекта> <в-объект>" -#: builtin/notes.c:27 +#: builtin/notes.c:28 msgid "" "git notes [--ref ] append [--allow-empty] [-m | -F |" " (-c | -C) ] []" msgstr "git notes [--ref <ссылка-на-заметку>] append [--allow-empty] [-m <сообщение> | -F <файл> | (-c | -C) <объект>] [<объект>]" -#: builtin/notes.c:28 +#: builtin/notes.c:29 msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref <ссылка-на-заметку>] edit [--allow-empty] [<объект>]" -#: builtin/notes.c:29 +#: builtin/notes.c:30 msgid "git notes [--ref ] show []" msgstr "git notes [--ref <ссылка-на-заметку>] show [<объект>]" -#: builtin/notes.c:30 +#: builtin/notes.c:31 msgid "" "git notes [--ref ] merge [-v | -q] [-s ] " msgstr "git notes [--ref <ссылка-на-заметку>] merge [-v | -q] [-s <стратегия>] <ссылка-на-заметку>" -#: builtin/notes.c:31 +#: builtin/notes.c:32 msgid "git notes merge --commit [-v | -q]" msgstr "git notes merge --commit [-v | -q]" -#: builtin/notes.c:32 +#: builtin/notes.c:33 msgid "git notes merge --abort [-v | -q]" msgstr "git notes merge --abort [-v | -q]" -#: builtin/notes.c:33 +#: builtin/notes.c:34 msgid "git notes [--ref ] remove [...]" msgstr "git notes [--ref <ссылка-на-заметку>] remove [<объект>…]" -#: builtin/notes.c:34 +#: builtin/notes.c:35 msgid "git notes [--ref ] prune [-n | -v]" msgstr "git notes [--ref <ссылка-на-заметку>] prune [-n | -v]" -#: builtin/notes.c:35 +#: builtin/notes.c:36 msgid "git notes [--ref ] get-ref" msgstr "git notes [--ref <ссылка-на-заметку>] get-ref" -#: builtin/notes.c:40 +#: builtin/notes.c:41 msgid "git notes [list []]" msgstr "git notes [list [<объект>]]" -#: builtin/notes.c:45 +#: builtin/notes.c:46 msgid "git notes add [] []" msgstr "git notes add [<опции>] [<объект>]" -#: builtin/notes.c:50 +#: builtin/notes.c:51 msgid "git notes copy [] " msgstr "git notes copy [<опции>] <из-объекта> <в-объект>" -#: builtin/notes.c:51 +#: builtin/notes.c:52 msgid "git notes copy --stdin [ ]..." msgstr "git notes copy --stdin [<из-объекта> <в-объект>]…" -#: builtin/notes.c:56 +#: builtin/notes.c:57 msgid "git notes append [] []" msgstr "git notes append [<опции>] [<объект>]" -#: builtin/notes.c:61 +#: builtin/notes.c:62 msgid "git notes edit []" msgstr "git notes edit [<объект>]" -#: builtin/notes.c:66 +#: builtin/notes.c:67 msgid "git notes show []" msgstr "git notes show [<объект>]" -#: builtin/notes.c:71 +#: builtin/notes.c:72 msgid "git notes merge [] " msgstr "git notes merge [<опции>] <ссылка-на-заметку>" -#: builtin/notes.c:72 +#: builtin/notes.c:73 msgid "git notes merge --commit []" msgstr "git notes merge --commit [<опции>]" -#: builtin/notes.c:73 +#: builtin/notes.c:74 msgid "git notes merge --abort []" msgstr "git notes merge --abort [<опции>]" -#: builtin/notes.c:78 +#: builtin/notes.c:79 msgid "git notes remove []" msgstr "git notes remove [<опции>]" -#: builtin/notes.c:83 +#: builtin/notes.c:84 msgid "git notes prune []" msgstr "git notes prune [<опции>]" -#: builtin/notes.c:88 +#: builtin/notes.c:89 msgid "git notes get-ref" msgstr "git notes get-ref" -#: builtin/notes.c:146 +#: builtin/notes.c:147 #, c-format msgid "unable to start 'show' for object '%s'" msgstr "не удалось запустить «show» для объекта «%s»" -#: builtin/notes.c:150 +#: builtin/notes.c:151 msgid "could not read 'show' output" msgstr "не удалось прочитать вывод «show»" -#: builtin/notes.c:158 +#: builtin/notes.c:159 #, c-format msgid "failed to finish 'show' for object '%s'" msgstr "не удалось завершить «show» для объекта «%s»" -#: builtin/notes.c:173 builtin/tag.c:477 +#: builtin/notes.c:174 builtin/tag.c:477 #, c-format msgid "could not create file '%s'" msgstr "не удалось создать файл «%s»" -#: builtin/notes.c:192 +#: builtin/notes.c:193 msgid "Please supply the note contents using either -m or -F option" msgstr "Пожалуйста, укажите содержимое заметки, используя опцию -m или -F" -#: builtin/notes.c:201 +#: builtin/notes.c:202 msgid "unable to write note object" msgstr "не удалось записать объект заметки" -#: builtin/notes.c:203 +#: builtin/notes.c:204 #, c-format msgid "The note contents have been left in %s" msgstr "Содержимое заметки осталось в %s" -#: builtin/notes.c:231 builtin/tag.c:693 +#: builtin/notes.c:232 builtin/tag.c:695 #, c-format msgid "cannot read '%s'" msgstr "не удалось прочитать «%s»" -#: builtin/notes.c:233 builtin/tag.c:696 +#: builtin/notes.c:234 builtin/tag.c:698 #, c-format msgid "could not open or read '%s'" msgstr "не удалось открыть или прочитать «%s»" -#: builtin/notes.c:252 builtin/notes.c:303 builtin/notes.c:305 -#: builtin/notes.c:365 builtin/notes.c:420 builtin/notes.c:506 -#: builtin/notes.c:511 builtin/notes.c:589 builtin/notes.c:652 -#: builtin/notes.c:854 builtin/tag.c:709 +#: builtin/notes.c:253 builtin/notes.c:304 builtin/notes.c:306 +#: builtin/notes.c:366 builtin/notes.c:421 builtin/notes.c:507 +#: builtin/notes.c:512 builtin/notes.c:590 builtin/notes.c:653 +#: builtin/notes.c:877 builtin/tag.c:711 #, c-format msgid "Failed to resolve '%s' as a valid ref." msgstr "Не удалось разрешить «%s» как ссылку." -#: builtin/notes.c:255 +#: builtin/notes.c:256 #, c-format msgid "Failed to read object '%s'." msgstr "Не удалось прочитать объект «%s»." -#: builtin/notes.c:259 +#: builtin/notes.c:260 #, c-format msgid "Cannot read note data from non-blob object '%s'." msgstr "Не удалось прочитать данные заметки из недвоичного объекта «%s»." -#: builtin/notes.c:299 -#, c-format -msgid "Malformed input line: '%s'." -msgstr "Плохая строка ввода: «%s»." - -#: builtin/notes.c:314 -#, c-format -msgid "Failed to copy notes from '%s' to '%s'" -msgstr "Не удалось скопировать заметку из «%s» в «%s»" - -#: builtin/notes.c:358 builtin/notes.c:413 builtin/notes.c:489 -#: builtin/notes.c:501 builtin/notes.c:577 builtin/notes.c:645 -#: builtin/notes.c:919 +#: builtin/notes.c:359 builtin/notes.c:414 builtin/notes.c:490 +#: builtin/notes.c:502 builtin/notes.c:578 builtin/notes.c:646 +#: builtin/notes.c:942 msgid "too many parameters" msgstr "передано слишком много параметров" -#: builtin/notes.c:371 builtin/notes.c:658 +#: builtin/notes.c:372 builtin/notes.c:659 #, c-format msgid "No note found for object %s." msgstr "Не найдена заметка для объекта %s." -#: builtin/notes.c:392 builtin/notes.c:555 +#: builtin/notes.c:393 builtin/notes.c:556 msgid "note contents as a string" msgstr "текстовое содержимое заметки" -#: builtin/notes.c:395 builtin/notes.c:558 +#: builtin/notes.c:396 builtin/notes.c:559 msgid "note contents in a file" msgstr "содержимое заметки в файле" -#: builtin/notes.c:397 builtin/notes.c:400 builtin/notes.c:560 -#: builtin/notes.c:563 builtin/tag.c:628 +#: builtin/notes.c:398 builtin/notes.c:401 builtin/notes.c:561 +#: builtin/notes.c:564 builtin/tag.c:630 msgid "object" msgstr "объект" -#: builtin/notes.c:398 builtin/notes.c:561 +#: builtin/notes.c:399 builtin/notes.c:562 msgid "reuse and edit specified note object" msgstr "использовать и отредактировать указанный объект заметки" -#: builtin/notes.c:401 builtin/notes.c:564 +#: builtin/notes.c:402 builtin/notes.c:565 msgid "reuse specified note object" msgstr "использовать указанный объект заметки" -#: builtin/notes.c:404 builtin/notes.c:567 +#: builtin/notes.c:405 builtin/notes.c:568 msgid "allow storing empty note" msgstr "разрешить сохранение пустой заметки" -#: builtin/notes.c:405 builtin/notes.c:476 +#: builtin/notes.c:406 builtin/notes.c:477 msgid "replace existing notes" msgstr "заменить существующие заметки" -#: builtin/notes.c:430 +#: builtin/notes.c:431 #, c-format msgid "" "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" msgstr "Не удалось добавить заметку. Найдена существующая заметка у объекта %s. Используйте параметр «-f» для перезаписи существующих заметок." -#: builtin/notes.c:445 builtin/notes.c:524 +#: builtin/notes.c:446 builtin/notes.c:525 #, c-format msgid "Overwriting existing notes for object %s\n" msgstr "Перезапись существующих заметок у объекта %s\n" -#: builtin/notes.c:456 builtin/notes.c:617 builtin/notes.c:859 +#: builtin/notes.c:457 builtin/notes.c:618 builtin/notes.c:882 #, c-format msgid "Removing note for object %s\n" msgstr "Удаление заметки у объекта %s\n" -#: builtin/notes.c:477 +#: builtin/notes.c:478 msgid "read objects from stdin" msgstr "прочитать объекты из стандартного ввода" -#: builtin/notes.c:479 +#: builtin/notes.c:480 msgid "load rewriting config for (implies --stdin)" msgstr "загрузить настройки перезаписи для команды <команда> (включает в себя --stdin)" -#: builtin/notes.c:497 +#: builtin/notes.c:498 msgid "too few parameters" msgstr "передано слишком мало параметров" -#: builtin/notes.c:518 +#: builtin/notes.c:519 #, c-format msgid "" "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite" " existing notes" msgstr "Не удалось скопировать заметку. Найдена существующая заметка у объекта %s. Используйте параметр «-f» для перезаписи существующих заметок." -#: builtin/notes.c:530 +#: builtin/notes.c:531 #, c-format msgid "Missing notes on source object %s. Cannot copy." msgstr "Нет заметок у исходного объекта %s. Нельзя скопировать." -#: builtin/notes.c:582 +#: builtin/notes.c:583 #, c-format msgid "" "The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n" "Please use 'git notes add -f -m/-F/-c/-C' instead.\n" msgstr "Опции -m/-F/-c/-C для подкоманды «edit» устарели.\nИспользуйте вместо них «git notes add -f -m/-F/-c/-C».\n" -#: builtin/notes.c:750 +#: builtin/notes.c:764 msgid "General options" msgstr "Общие опции" -#: builtin/notes.c:752 +#: builtin/notes.c:766 msgid "Merge options" msgstr "Опции слияния" -#: builtin/notes.c:754 +#: builtin/notes.c:768 msgid "" "resolve notes conflicts using the given strategy " "(manual/ours/theirs/union/cat_sort_uniq)" msgstr "разрешить конфликты заметок с помощью указанной стратегии (manual/ours/theirs/union/cat_sort_uniq)" -#: builtin/notes.c:756 +#: builtin/notes.c:770 msgid "Committing unmerged notes" msgstr "Коммит не слитых заметок" -#: builtin/notes.c:758 +#: builtin/notes.c:772 msgid "finalize notes merge by committing unmerged notes" msgstr "завершить слияние заметок коммитом не слитых заметок" -#: builtin/notes.c:760 +#: builtin/notes.c:774 msgid "Aborting notes merge resolution" msgstr "Отмена разрешения слияния заметок" -#: builtin/notes.c:762 +#: builtin/notes.c:776 msgid "abort notes merge" msgstr "отменить слияние заметок" -#: builtin/notes.c:857 +#: builtin/notes.c:853 +#, c-format +msgid "A notes merge into %s is already in-progress at %s" +msgstr "Слияние заметок в %s уже выполняется на %s" + +#: builtin/notes.c:880 #, c-format msgid "Object %s has no note\n" msgstr "У объекта %s нет заметки\n" -#: builtin/notes.c:869 +#: builtin/notes.c:892 msgid "attempt to remove non-existent note is not an error" msgstr "попытка удаления несуществующей заметки не является ошибкой" -#: builtin/notes.c:872 +#: builtin/notes.c:895 msgid "read object names from the standard input" msgstr "прочитать имена объектов из стандартного ввода" -#: builtin/notes.c:953 +#: builtin/notes.c:976 msgid "notes-ref" msgstr "ссылка-на-заметку" -#: builtin/notes.c:954 +#: builtin/notes.c:977 msgid "use notes from " msgstr "использовать заметку из <ссылка-на-заметку>" -#: builtin/notes.c:989 builtin/remote.c:1618 +#: builtin/notes.c:1012 builtin/remote.c:1588 #, c-format msgid "Unknown subcommand: %s" msgstr "Неизвестная подкоманда: %s" #: builtin/pack-objects.c:28 -msgid "git pack-objects --stdout [options...] [< ref-list | < object-list]" -msgstr "git pack-objects --stdout [опции…] [< список-ссылок | < список-объектов]" +msgid "" +"git pack-objects --stdout [...] [< | < ]" +msgstr "git pack-objects --stdout [<опции>…] [< <список-ссылок> | < <список-объектов>]" #: builtin/pack-objects.c:29 -msgid "git pack-objects [options...] base-name [< ref-list | < object-list]" -msgstr "git pack-objects [опции…] имя-базы [< список-ссылок | < список-объектов]" +msgid "" +"git pack-objects [...] [< | < ]" +msgstr "git pack-objects [<опции>…] <имя-базы> [< <список-ссылок> | < <список-объектов>]" #: builtin/pack-objects.c:175 builtin/pack-objects.c:178 #, c-format @@ -7671,153 +8097,143 @@ msgstr "неподдерживаемая версия индекса %s" msgid "bad index version '%s'" msgstr "плохая версия индекса «%s»" -#: builtin/pack-objects.c:2595 -#, c-format -msgid "option %s does not accept negative form" -msgstr "опция %s не принимает отрицательные значения" - -#: builtin/pack-objects.c:2599 -#, c-format -msgid "unable to parse value '%s' for option %s" -msgstr "не удалось разобрать значение «%s» для опции %s" - -#: builtin/pack-objects.c:2619 +#: builtin/pack-objects.c:2602 msgid "do not show progress meter" msgstr "не выводить прогресс выполнения" -#: builtin/pack-objects.c:2621 +#: builtin/pack-objects.c:2604 msgid "show progress meter" msgstr "показать прогресс выполнения" -#: builtin/pack-objects.c:2623 +#: builtin/pack-objects.c:2606 msgid "show progress meter during object writing phase" msgstr "показать прогресс выполнения во время записи объектов" -#: builtin/pack-objects.c:2626 +#: builtin/pack-objects.c:2609 msgid "similar to --all-progress when progress meter is shown" msgstr "похоже на --all-progress при включенном прогрессе выполнения" -#: builtin/pack-objects.c:2627 +#: builtin/pack-objects.c:2610 msgid "version[,offset]" msgstr "версия[,смещение]" -#: builtin/pack-objects.c:2628 +#: builtin/pack-objects.c:2611 msgid "write the pack index file in the specified idx format version" msgstr "записать файл индекса пакета в указанной версии формата" -#: builtin/pack-objects.c:2631 +#: builtin/pack-objects.c:2614 msgid "maximum size of each output pack file" msgstr "максимальный размер каждого выходного файла пакета" -#: builtin/pack-objects.c:2633 +#: builtin/pack-objects.c:2616 msgid "ignore borrowed objects from alternate object store" msgstr "игнорировать чужие объекты, взятые из альтернативного хранилища объектов" -#: builtin/pack-objects.c:2635 +#: builtin/pack-objects.c:2618 msgid "ignore packed objects" msgstr "игнорировать упакованные объекты" -#: builtin/pack-objects.c:2637 +#: builtin/pack-objects.c:2620 msgid "limit pack window by objects" msgstr "ограничить окно пакета по количеству объектов" -#: builtin/pack-objects.c:2639 +#: builtin/pack-objects.c:2622 msgid "limit pack window by memory in addition to object limit" msgstr "дополнительно к количеству объектов ограничить окно пакета по памяти" -#: builtin/pack-objects.c:2641 +#: builtin/pack-objects.c:2624 msgid "maximum length of delta chain allowed in the resulting pack" msgstr "максимальная разрешенная длина цепочки дельт в результирующем пакете" -#: builtin/pack-objects.c:2643 +#: builtin/pack-objects.c:2626 msgid "reuse existing deltas" msgstr "использовать повторно существующие дельты" -#: builtin/pack-objects.c:2645 +#: builtin/pack-objects.c:2628 msgid "reuse existing objects" msgstr "использовать повторно существующие объекты" -#: builtin/pack-objects.c:2647 +#: builtin/pack-objects.c:2630 msgid "use OFS_DELTA objects" msgstr "использовать объекты OFS_DELTA" -#: builtin/pack-objects.c:2649 +#: builtin/pack-objects.c:2632 msgid "use threads when searching for best delta matches" msgstr "использовать многопоточность при поиске лучших совпадений дельт" -#: builtin/pack-objects.c:2651 +#: builtin/pack-objects.c:2634 msgid "do not create an empty pack output" msgstr "не создавать пустые выходные пакеты" -#: builtin/pack-objects.c:2653 +#: builtin/pack-objects.c:2636 msgid "read revision arguments from standard input" msgstr "прочитать аргументы редакций из стандартного ввода" -#: builtin/pack-objects.c:2655 +#: builtin/pack-objects.c:2638 msgid "limit the objects to those that are not yet packed" msgstr "ограничиться объектами, которые еще не упакованы" -#: builtin/pack-objects.c:2658 +#: builtin/pack-objects.c:2641 msgid "include objects reachable from any reference" msgstr "включить объекты, которые достижимы по любой из ссылок" -#: builtin/pack-objects.c:2661 +#: builtin/pack-objects.c:2644 msgid "include objects referred by reflog entries" msgstr "включить объекты, на которые ссылаются записи журнала ссылок" -#: builtin/pack-objects.c:2664 +#: builtin/pack-objects.c:2647 msgid "include objects referred to by the index" msgstr "включить объекты, на которые ссылается индекс" -#: builtin/pack-objects.c:2667 +#: builtin/pack-objects.c:2650 msgid "output pack to stdout" msgstr "вывести пакет на стандартный вывод" -#: builtin/pack-objects.c:2669 +#: builtin/pack-objects.c:2652 msgid "include tag objects that refer to objects to be packed" msgstr "включить объекты меток, которые ссылаются на упаковываемые объекты" -#: builtin/pack-objects.c:2671 +#: builtin/pack-objects.c:2654 msgid "keep unreachable objects" msgstr "сохранять ссылки на недоступные объекты" -#: builtin/pack-objects.c:2672 parse-options.h:139 +#: builtin/pack-objects.c:2655 parse-options.h:142 msgid "time" msgstr "время" -#: builtin/pack-objects.c:2673 +#: builtin/pack-objects.c:2656 msgid "unpack unreachable objects newer than