From 3270741ea8c2a225183d272bf19ea19d5b3c05d8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 26 Sep 2016 18:09:48 -0700 Subject: [PATCH 0001/1540] utf8: refactor code to decide fallback encoding The codepath we use to call iconv_open() has a provision to use a fallback encoding when it fails, hoping that "UTF-8" being spelled differently could be the reason why the library function did not like the encoding names we gave it. Essentially, we turn what we have observed to be used as variants of "UTF-8" (e.g. "utf8") into the most official spelling and use that as a fallback. We do the same thing for input and output encoding. Introduce a helper function to do just one side and call that twice. Signed-off-by: Junio C Hamano --- utf8.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/utf8.c b/utf8.c index 00e10c86ad7ea7..550e785ecbf058 100644 --- a/utf8.c +++ b/utf8.c @@ -489,6 +489,21 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv, int *outs return out; } +static const char *fallback_encoding(const char *name) +{ + /* + * Some platforms do not have the variously spelled variants of + * UTF-8, so let's fall back to trying the most official + * spelling. We do so only as a fallback in case the platform + * does understand the user's spelling, but not our official + * one. + */ + if (is_encoding_utf8(name)) + return "UTF-8"; + + return name; +} + char *reencode_string_len(const char *in, int insz, const char *out_encoding, const char *in_encoding, int *outsz) @@ -501,17 +516,9 @@ char *reencode_string_len(const char *in, int insz, conv = iconv_open(out_encoding, in_encoding); if (conv == (iconv_t) -1) { - /* - * Some platforms do not have the variously spelled variants of - * UTF-8, so let's fall back to trying the most official - * spelling. We do so only as a fallback in case the platform - * does understand the user's spelling, but not our official - * one. - */ - if (is_encoding_utf8(in_encoding)) - in_encoding = "UTF-8"; - if (is_encoding_utf8(out_encoding)) - out_encoding = "UTF-8"; + in_encoding = fallback_encoding(in_encoding); + out_encoding = fallback_encoding(out_encoding); + conv = iconv_open(out_encoding, in_encoding); if (conv == (iconv_t) -1) return NULL; From df3755888b9a54e69ab70881738d431587c57951 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 26 Sep 2016 18:09:48 -0700 Subject: [PATCH 0002/1540] utf8: accept "latin-1" as ISO-8859-1 Even though latin-1 is still seen in e-mail headers, some platforms only install ISO-8859-1. "iconv -f ISO-8859-1" succeeds, while "iconv -f latin-1" fails on such a system. Using the same fallback_encoding() mechanism factored out in the previous step, teach ourselves that "ISO-8859-1" has a better chance of being accepted than "latin-1". Signed-off-by: Junio C Hamano --- utf8.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/utf8.c b/utf8.c index 550e785ecbf058..0c8e011a58cae3 100644 --- a/utf8.c +++ b/utf8.c @@ -501,6 +501,13 @@ static const char *fallback_encoding(const char *name) if (is_encoding_utf8(name)) return "UTF-8"; + /* + * Even though latin-1 is still seen in e-mail + * headers, some platforms only install ISO-8859-1. + */ + if (!strcasecmp(name, "latin-1")) + return "ISO-8859-1"; + return name; } From 1b8ac5ead520146802debd52cb38e5c27b3483a2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 28 Oct 2016 06:23:07 -0700 Subject: [PATCH 0003/1540] git_open(): untangle possible NOATIME and CLOEXEC interactions The way we structured the fallback/retry mechanism for opening with O_NOATIME and O_CLOEXEC meant that if we failed due to lack of support to open the file with O_NOATIME option (i.e. EINVAL), we would still try to drop O_CLOEXEC first and retry, and then drop O_NOATIME. A platform on which O_NOATIME is defined in the header without support from the kernel wouldn't have a chance to open with O_CLOEXEC option due to this code structure. Arguably, O_CLOEXEC is more important than O_NOATIME, as the latter is mostly about performance, while the former can affect correctness. Instead use O_CLOEXEC to open the file, and then use fcntl(2) to set O_NOATIME on the resulting file descriptor. open(2) itself does not cause atime to be updated according to Linus [*1*]. The helper to do the former can be usable in the codepath in ce_compare_data() that was recently added to open a file descriptor with O_CLOEXEC; use it while we are at it. *1* Signed-off-by: Junio C Hamano --- cache.h | 1 + read-cache.c | 9 +-------- sha1_file.c | 39 +++++++++++++++++++-------------------- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/cache.h b/cache.h index a902ca1f8e4d63..6f1c21a35240ad 100644 --- a/cache.h +++ b/cache.h @@ -1122,6 +1122,7 @@ extern int write_sha1_file(const void *buf, unsigned long len, const char *type, extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); +extern int git_open_cloexec(const char *name, int flags); extern int git_open(const char *name); extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size); extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); diff --git a/read-cache.c b/read-cache.c index db5d910642663e..c27d3e240b06fc 100644 --- a/read-cache.c +++ b/read-cache.c @@ -156,14 +156,7 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) static int ce_compare_data(const struct cache_entry *ce, struct stat *st) { int match = -1; - static int cloexec = O_CLOEXEC; - int fd = open(ce->name, O_RDONLY | cloexec); - - if ((cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { - /* Try again w/o O_CLOEXEC: the kernel might not support it */ - cloexec &= ~O_CLOEXEC; - fd = open(ce->name, O_RDONLY | cloexec); - } + int fd = git_open_cloexec(ce->name, O_RDONLY); if (fd >= 0) { unsigned char sha1[20]; diff --git a/sha1_file.c b/sha1_file.c index 09045df1dcd8cf..dfbf398183d5b9 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1559,31 +1559,30 @@ int check_sha1_signature(const unsigned char *sha1, void *map, return hashcmp(sha1, real_sha1) ? -1 : 0; } -int git_open(const char *name) +int git_open_cloexec(const char *name, int flags) { - static int sha1_file_open_flag = O_NOATIME | O_CLOEXEC; - - for (;;) { - int fd; - - errno = 0; - fd = open(name, O_RDONLY | sha1_file_open_flag); - if (fd >= 0) - return fd; + static int cloexec = O_CLOEXEC; + int fd = open(name, flags | cloexec); + if ((cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { /* Try again w/o O_CLOEXEC: the kernel might not support it */ - if ((sha1_file_open_flag & O_CLOEXEC) && errno == EINVAL) { - sha1_file_open_flag &= ~O_CLOEXEC; - continue; - } + cloexec &= ~O_CLOEXEC; + fd = open(name, flags | cloexec); + } + return fd; +} - /* Might the failure be due to O_NOATIME? */ - if (errno != ENOENT && (sha1_file_open_flag & O_NOATIME)) { - sha1_file_open_flag &= ~O_NOATIME; - continue; - } - return -1; +int git_open(const char *name) +{ + static int noatime = O_NOATIME; + int fd = git_open_cloexec(name, O_RDONLY); + + if (0 <= fd && (noatime & O_NOATIME)) { + int flags = fcntl(fd, F_GETFL); + if (fcntl(fd, F_SETFL, flags | noatime)) + noatime = 0; } + return fd; } static int stat_sha1_file(const unsigned char *sha1, struct stat *st) From 1e3001a8e2386a1433d1769a5a78007596bbc04d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 31 Oct 2016 10:41:41 -0700 Subject: [PATCH 0004/1540] git_open_cloexec(): use fcntl(2) w/ FD_CLOEXEC fallback A platform might not support open(2) with O_CLOEXEC but may support telling the same with fcntl(2) to flip FD_CLOEXEC bit on on an open file descriptor. It is a fallback that is inherently racy and this may not be worth doing, though. Suggested-by: Linus Torvalds Signed-off-by: Junio C Hamano --- sha1_file.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index dfbf398183d5b9..64e1a21fc61ed2 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1561,14 +1561,28 @@ int check_sha1_signature(const unsigned char *sha1, void *map, int git_open_cloexec(const char *name, int flags) { - static int cloexec = O_CLOEXEC; - int fd = open(name, flags | cloexec); + int fd; + static int o_cloexec = O_CLOEXEC; - if ((cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { + fd = open(name, flags | o_cloexec); + if ((o_cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { /* Try again w/o O_CLOEXEC: the kernel might not support it */ - cloexec &= ~O_CLOEXEC; - fd = open(name, flags | cloexec); + o_cloexec &= ~O_CLOEXEC; + fd = open(name, flags | o_cloexec); } + +#if defined(F_GETFL) && defined(F_SETFL) && defined(FD_CLOEXEC) + { + static int fd_cloexec = FD_CLOEXEC; + + if (!o_cloexec && 0 <= fd && fd_cloexec) { + /* Opened w/o O_CLOEXEC? try with fcntl(2) to add it */ + int flags = fcntl(fd, F_GETFL); + if (fcntl(fd, F_SETFL, flags | fd_cloexec)) + fd_cloexec = 0; + } + } +#endif return fd; } From b4d065df03049bacfbc40467b60b13e804b7d289 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 28 Oct 2016 06:29:27 -0700 Subject: [PATCH 0005/1540] sha1_file: stop opening files with O_NOATIME When we open object files, we try to do so with O_NOATIME. This dates back to 144bde78e9 (Use O_NOATIME when opening the sha1 files., 2005-04-23), which is an optimization to avoid creating a bunch of dirty inodes when we're accessing many objects. But a few things have changed since then: 1. In June 2005, git learned about packfiles, which means we would do a lot fewer atime updates (rather than one per object access, we'd generally get one per packfile). 2. In late 2006, Linux learned about "relatime", which is generally the default on modern installs. So performance around atimes updates is a non-issue there these days. All the world isn't Linux, but as it turns out, Linux is the only platform to implement O_NOATIME in the first place. So it's very unlikely that this code is helping anybody these days. Helped-by: Jeff King [jc: took idea and log message from peff] Signed-off-by: Junio C Hamano --- cache.h | 2 +- sha1_file.c | 21 --------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/cache.h b/cache.h index 6f1c21a35240ad..f440d3fd1ebdca 100644 --- a/cache.h +++ b/cache.h @@ -1123,7 +1123,7 @@ extern int hash_sha1_file_literally(const void *buf, unsigned long len, const ch extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); extern int git_open_cloexec(const char *name, int flags); -extern int git_open(const char *name); +#define git_open(name) git_open_cloexec(name, O_RDONLY) extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size); extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); extern int parse_sha1_header(const char *hdr, unsigned long *sizep); diff --git a/sha1_file.c b/sha1_file.c index 64e1a21fc61ed2..4e062edea0f529 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -27,14 +27,6 @@ #include "list.h" #include "mergesort.h" -#ifndef O_NOATIME -#if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) -#define O_NOATIME 01000000 -#else -#define O_NOATIME 0 -#endif -#endif - #define SZ_FMT PRIuMAX static inline uintmax_t sz_fmt(size_t s) { return s; } @@ -1586,19 +1578,6 @@ int git_open_cloexec(const char *name, int flags) return fd; } -int git_open(const char *name) -{ - static int noatime = O_NOATIME; - int fd = git_open_cloexec(name, O_RDONLY); - - if (0 <= fd && (noatime & O_NOATIME)) { - int flags = fcntl(fd, F_GETFL); - if (fcntl(fd, F_SETFL, flags | noatime)) - noatime = 0; - } - return fd; -} - static int stat_sha1_file(const unsigned char *sha1, struct stat *st) { struct alternate_object_database *alt; From 8de7eeb54b6aaa6d429b5d9c2b667847c35480ff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Nov 2016 17:42:40 -0800 Subject: [PATCH 0006/1540] compression: unify pack.compression configuration parsing There are three codepaths that use a variable whose name is pack_compression_level to affect how objects and deltas sent to a packfile is compressed. Unlike zlib_compression_level that controls the loose object compression, however, this variable was static to each of these codepaths. Two of them read the pack.compression configuration variable, using core.compression as the default, and one of them also allowed overriding it from the command line. The other codepath in bulk-checkin did not pay any attention to the configuration. Unify the configuration parsing to git_default_config(), where we implement the parsing of core.loosecompression and core.compression and make the former override the latter, by moving code to parse pack.compression and also allow core.compression to give default to this variable. Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 14 ------ bulk-checkin.c | 2 - cache.h | 2 +- config.c | 16 +++++++ environment.c | 2 +- fast-import.c | 13 ------ t/t1050-large.sh | 29 +++++++++++++ t/t5315-pack-objects-compression.sh | 44 +++++++++++++++++++ t/t9303-fast-import-compression.sh | 67 +++++++++++++++++++++++++++++ 9 files changed, 158 insertions(+), 31 deletions(-) create mode 100755 t/t5315-pack-objects-compression.sh create mode 100755 t/t9303-fast-import-compression.sh diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 0fd52bd6b4b985..8841f8b366b4ce 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -61,8 +61,6 @@ static int delta_search_threads; static int pack_to_stdout; static int num_preferred_base; static struct progress *progress_state; -static int pack_compression_level = Z_DEFAULT_COMPRESSION; -static int pack_compression_seen; static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; @@ -2368,16 +2366,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) depth = git_config_int(k, v); return 0; } - if (!strcmp(k, "pack.compression")) { - int level = git_config_int(k, v); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - pack_compression_seen = 1; - return 0; - } if (!strcmp(k, "pack.deltacachesize")) { max_delta_cache_size = git_config_int(k, v); return 0; @@ -2869,8 +2857,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); - if (!pack_compression_seen && core_compression_seen) - pack_compression_level = core_compression_level; progress = isatty(2); argc = parse_options(argc, argv, prefix, pack_objects_options, diff --git a/bulk-checkin.c b/bulk-checkin.c index 4347f5c76aa728..991b4a13e24910 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -7,8 +7,6 @@ #include "pack.h" #include "strbuf.h" -static int pack_compression_level = Z_DEFAULT_COMPRESSION; - static struct bulk_checkin_state { unsigned plugged:1; diff --git a/cache.h b/cache.h index a50a61a19787de..b14c0e8e38498f 100644 --- a/cache.h +++ b/cache.h @@ -670,7 +670,7 @@ extern const char *git_attributes_file; extern const char *git_hooks_path; extern int zlib_compression_level; extern int core_compression_level; -extern int core_compression_seen; +extern int pack_compression_level; extern size_t packed_git_window_size; extern size_t packed_git_limit; extern size_t delta_base_cache_limit; diff --git a/config.c b/config.c index 83fdecb1bc9f6f..2f1aef742ef5c5 100644 --- a/config.c +++ b/config.c @@ -66,6 +66,8 @@ static struct key_value_info *current_config_kvi; */ static enum config_scope current_parsing_scope; +static int core_compression_seen; +static int pack_compression_seen; static int zlib_compression_seen; /* @@ -865,6 +867,8 @@ static int git_default_core_config(const char *var, const char *value) core_compression_seen = 1; if (!zlib_compression_seen) zlib_compression_level = level; + if (!pack_compression_seen) + pack_compression_level = level; return 0; } @@ -1125,6 +1129,18 @@ int git_default_config(const char *var, const char *value, void *dummy) pack_size_limit_cfg = git_config_ulong(var, value); return 0; } + + if (!strcmp(var, "pack.compression")) { + int level = git_config_int(var, value); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die(_("bad pack compression level %d"), level); + pack_compression_level = level; + pack_compression_seen = 1; + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index 0935ec696e530e..4bce3eebfafa45 100644 --- a/environment.c +++ b/environment.c @@ -34,7 +34,7 @@ const char *git_attributes_file; const char *git_hooks_path; int zlib_compression_level = Z_BEST_SPEED; int core_compression_level; -int core_compression_seen; +int pack_compression_level = Z_DEFAULT_COMPRESSION; int fsync_object_files; size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; diff --git a/fast-import.c b/fast-import.c index cb545d7df514b7..f561ba833bd75b 100644 --- a/fast-import.c +++ b/fast-import.c @@ -284,8 +284,6 @@ static unsigned long max_depth = 10; static off_t max_packsize; static int unpack_limit = 100; static int force_update; -static int pack_compression_level = Z_DEFAULT_COMPRESSION; -static int pack_compression_seen; /* Stats and misc. counters */ static uintmax_t alloc_count; @@ -3381,15 +3379,6 @@ static void git_pack_config(void) if (max_depth > MAX_DEPTH) max_depth = MAX_DEPTH; } - if (!git_config_get_int("pack.compression", &pack_compression_level)) { - if (pack_compression_level == -1) - pack_compression_level = Z_DEFAULT_COMPRESSION; - else if (pack_compression_level < 0 || - pack_compression_level > Z_BEST_COMPRESSION) - git_die_config("pack.compression", - "bad pack compression level %d", pack_compression_level); - pack_compression_seen = 1; - } if (!git_config_get_int("pack.indexversion", &indexversion_value)) { pack_idx_opts.version = indexversion_value; if (pack_idx_opts.version > 2) @@ -3454,8 +3443,6 @@ int cmd_main(int argc, const char **argv) setup_git_directory(); reset_pack_idx_option(&pack_idx_opts); git_pack_config(); - if (!pack_compression_seen && core_compression_seen) - pack_compression_level = core_compression_level; alloc_objects(object_entry_alloc); strbuf_init(&command_buf, 0); diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 096dbffecc3d51..6fd264cff0d6de 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -5,6 +5,12 @@ test_description='adding and checking out large blobs' . ./test-lib.sh +# This should be moved to test-lib.sh together with the +# copy in t0021 after both topics have graduated to 'master'. +file_size () { + perl -e 'print -s $ARGV[0]' "$1" +} + test_expect_success setup ' # clone does not allow us to pass core.bigfilethreshold to # new repos, so set core.bigfilethreshold globally @@ -17,6 +23,29 @@ test_expect_success setup ' export GIT_ALLOC_LIMIT ' +# add a large file with different settings +while read expect config +do + test_expect_success "add with $config" ' + test_when_finished "rm -f .git/objects/pack/pack-*.* .git/index" && + git $config add large1 && + sz=$(file_size .git/objects/pack/pack-*.pack) && + case "$expect" in + small) test "$sz" -le 100000 ;; + large) test "$sz" -ge 100000 ;; + esac + ' +done <<\EOF +large -c core.compression=0 +small -c core.compression=9 +large -c core.compression=0 -c pack.compression=0 +large -c core.compression=9 -c pack.compression=0 +small -c core.compression=0 -c pack.compression=9 +small -c core.compression=9 -c pack.compression=9 +large -c pack.compression=0 +small -c pack.compression=9 +EOF + test_expect_success 'add a large file or two' ' git add large1 huge large2 && # make sure we got a single packfile and no loose objects diff --git a/t/t5315-pack-objects-compression.sh b/t/t5315-pack-objects-compression.sh new file mode 100755 index 00000000000000..34c47dae09966b --- /dev/null +++ b/t/t5315-pack-objects-compression.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='pack-object compression configuration' + +. ./test-lib.sh + +# This should be moved to test-lib.sh together with the +# copy in t0021 after both topics have graduated to 'master'. +file_size () { + perl -e 'print -s $ARGV[0]' "$1" +} + +test_expect_success setup ' + printf "%2000000s" X | + git hash-object -w --stdin >object-name && + # make sure it resulted in a loose object + ob=$(sed -e "s/\(..\).*/\1/" object-name) && + ject=$(sed -e "s/..\(.*\)/\1/" object-name) && + test -f .git/objects/$ob/$ject +' + +while read expect config +do + test_expect_success "pack-objects with $config" ' + test_when_finished "rm -f pack-*.*" && + git $config pack-objects pack Date: Tue, 22 Nov 2016 12:14:36 -0800 Subject: [PATCH 0007/1540] submodule config: inline config_from_{name, path} There is no other user of config_from_{name, path}, such that there is no reason for the existence of these one liner functions. Just inline these to increase readability. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano Reviewed-by: Brandon Williams Signed-off-by: Junio C Hamano --- submodule-config.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/submodule-config.c b/submodule-config.c index 098085be69b976..15ffab6af40f37 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -471,18 +471,6 @@ static const struct submodule *config_from(struct submodule_cache *cache, return submodule; } -static const struct submodule *config_from_path(struct submodule_cache *cache, - const unsigned char *commit_sha1, const char *path) -{ - return config_from(cache, commit_sha1, path, lookup_path); -} - -static const struct submodule *config_from_name(struct submodule_cache *cache, - const unsigned char *commit_sha1, const char *name) -{ - return config_from(cache, commit_sha1, name, lookup_name); -} - static void ensure_cache_init(void) { if (is_cache_init) @@ -508,14 +496,14 @@ const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name) { ensure_cache_init(); - return config_from_name(&the_submodule_cache, commit_sha1, name); + return config_from(&the_submodule_cache, commit_sha1, name, lookup_name); } const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path) { ensure_cache_init(); - return config_from_path(&the_submodule_cache, commit_sha1, path); + return config_from(&the_submodule_cache, commit_sha1, path, lookup_path); } void submodule_free(void) From 73c293bb6c15992690b16c90bcac243a76d86400 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 22 Nov 2016 12:14:37 -0800 Subject: [PATCH 0008/1540] submodule-config: rename commit_sha1 to treeish_name It is also possible to pass in any treeish name to lookup a submodule config. Make it clear by naming the variables accordingly. Looking up a submodule config by tree hash will come in handy in a later patch. Signed-off-by: Stefan Beller Reviewed-by: Brandon Williams Signed-off-by: Junio C Hamano --- .../technical/api-submodule-config.txt | 9 ++-- submodule-config.c | 46 +++++++++---------- submodule-config.h | 4 +- t/t7411-submodule-config.sh | 14 ++++++ 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/Documentation/technical/api-submodule-config.txt b/Documentation/technical/api-submodule-config.txt index 941fa178dd8661..8285bcc605ecfa 100644 --- a/Documentation/technical/api-submodule-config.txt +++ b/Documentation/technical/api-submodule-config.txt @@ -47,15 +47,16 @@ Functions Can be passed to the config parsing infrastructure to parse local (worktree) submodule configurations. -`const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path)`:: +`const struct submodule *submodule_from_path(const unsigned char *treeish_name, const char *path)`:: - Lookup values for one submodule by its commit_sha1 and path. + Given a tree-ish in the superproject and a path, return the + submodule that is bound at the path in the named tree. -`const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name)`:: +`const struct submodule *submodule_from_name(const unsigned char *treeish_name, const char *name)`:: The same as above but lookup by name. -If given the null_sha1 as commit_sha1 the local configuration of a +If given the null_sha1 as treeish_name the local configuration of a submodule will be returned (e.g. consolidated values from local git configuration and the .gitmodules file in the worktree). diff --git a/submodule-config.c b/submodule-config.c index 15ffab6af40f37..ec13ab5a3dd82a 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -263,12 +263,12 @@ int parse_push_recurse_submodules_arg(const char *opt, const char *arg) return parse_push_recurse(opt, arg, 1); } -static void warn_multiple_config(const unsigned char *commit_sha1, +static void warn_multiple_config(const unsigned char *treeish_name, const char *name, const char *option) { const char *commit_string = "WORKTREE"; - if (commit_sha1) - commit_string = sha1_to_hex(commit_sha1); + if (treeish_name) + commit_string = sha1_to_hex(treeish_name); warning("%s:.gitmodules, multiple configurations found for " "'submodule.%s.%s'. Skipping second one!", commit_string, name, option); @@ -276,7 +276,7 @@ static void warn_multiple_config(const unsigned char *commit_sha1, struct parse_config_parameter { struct submodule_cache *cache; - const unsigned char *commit_sha1; + const unsigned char *treeish_name; const unsigned char *gitmodules_sha1; int overwrite; }; @@ -300,7 +300,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!value) ret = config_error_nonbool(var); else if (!me->overwrite && submodule->path) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "path"); else { if (submodule->path) @@ -314,7 +314,7 @@ static int parse_config(const char *var, const char *value, void *data) int die_on_error = is_null_sha1(me->gitmodules_sha1); if (!me->overwrite && submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "fetchrecursesubmodules"); else submodule->fetch_recurse = parse_fetch_recurse( @@ -324,7 +324,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!value) ret = config_error_nonbool(var); else if (!me->overwrite && submodule->ignore) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "ignore"); else if (strcmp(value, "untracked") && strcmp(value, "dirty") && @@ -340,7 +340,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!value) { ret = config_error_nonbool(var); } else if (!me->overwrite && submodule->url) { - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "url"); } else { free((void *) submodule->url); @@ -351,21 +351,21 @@ static int parse_config(const char *var, const char *value, void *data) ret = config_error_nonbool(var); else if (!me->overwrite && submodule->update_strategy.type != SM_UPDATE_UNSPECIFIED) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "update"); else if (parse_submodule_update_strategy(value, &submodule->update_strategy) < 0) die(_("invalid value for %s"), var); } else if (!strcmp(item.buf, "shallow")) { if (!me->overwrite && submodule->recommend_shallow != -1) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "shallow"); else submodule->recommend_shallow = git_config_bool(var, value); } else if (!strcmp(item.buf, "branch")) { if (!me->overwrite && submodule->branch) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "branch"); else { free((void *)submodule->branch); @@ -379,18 +379,18 @@ static int parse_config(const char *var, const char *value, void *data) return ret; } -static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, +static int gitmodule_sha1_from_commit(const unsigned char *treeish_name, unsigned char *gitmodules_sha1, struct strbuf *rev) { int ret = 0; - if (is_null_sha1(commit_sha1)) { + if (is_null_sha1(treeish_name)) { hashclr(gitmodules_sha1); return 1; } - strbuf_addf(rev, "%s:.gitmodules", sha1_to_hex(commit_sha1)); + strbuf_addf(rev, "%s:.gitmodules", sha1_to_hex(treeish_name)); if (get_sha1(rev->buf, gitmodules_sha1) >= 0) ret = 1; @@ -402,7 +402,7 @@ static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, * revisions. */ static const struct submodule *config_from(struct submodule_cache *cache, - const unsigned char *commit_sha1, const char *key, + const unsigned char *treeish_name, const char *key, enum lookup_type lookup_type) { struct strbuf rev = STRBUF_INIT; @@ -418,7 +418,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, * return the first submodule. Can be used to check whether * there are any submodules parsed. */ - if (!commit_sha1 || !key) { + if (!treeish_name || !key) { struct hashmap_iter iter; struct submodule_entry *entry; @@ -428,7 +428,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, return entry->config; } - if (!gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) + if (!gitmodule_sha1_from_commit(treeish_name, sha1, &rev)) goto out; switch (lookup_type) { @@ -448,7 +448,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, /* fill the submodule config into the cache */ parameter.cache = cache; - parameter.commit_sha1 = commit_sha1; + parameter.treeish_name = treeish_name; parameter.gitmodules_sha1 = sha1; parameter.overwrite = 0; git_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf, @@ -484,7 +484,7 @@ int parse_submodule_config_option(const char *var, const char *value) { struct parse_config_parameter parameter; parameter.cache = &the_submodule_cache; - parameter.commit_sha1 = NULL; + parameter.treeish_name = NULL; parameter.gitmodules_sha1 = null_sha1; parameter.overwrite = 1; @@ -492,18 +492,18 @@ int parse_submodule_config_option(const char *var, const char *value) return parse_config(var, value, ¶meter); } -const struct submodule *submodule_from_name(const unsigned char *commit_sha1, +const struct submodule *submodule_from_name(const unsigned char *treeish_name, const char *name) { ensure_cache_init(); - return config_from(&the_submodule_cache, commit_sha1, name, lookup_name); + return config_from(&the_submodule_cache, treeish_name, name, lookup_name); } -const struct submodule *submodule_from_path(const unsigned char *commit_sha1, +const struct submodule *submodule_from_path(const unsigned char *treeish_name, const char *path) { ensure_cache_init(); - return config_from(&the_submodule_cache, commit_sha1, path, lookup_path); + return config_from(&the_submodule_cache, treeish_name, path, lookup_path); } void submodule_free(void) diff --git a/submodule-config.h b/submodule-config.h index d05c542d2cdace..99df8e593cc7c2 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -25,9 +25,9 @@ struct submodule { int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); int parse_push_recurse_submodules_arg(const char *opt, const char *arg); int parse_submodule_config_option(const char *var, const char *value); -const struct submodule *submodule_from_name(const unsigned char *commit_sha1, +const struct submodule *submodule_from_name(const unsigned char *commit_or_tree, const char *name); -const struct submodule *submodule_from_path(const unsigned char *commit_sha1, +const struct submodule *submodule_from_path(const unsigned char *commit_or_tree, const char *path); void submodule_free(void); diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index 47562ce4654db9..d389ae5408ab78 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -93,6 +93,20 @@ test_expect_success 'error message contains blob reference' ' ) ' +test_expect_success 'using different treeishs works' ' + ( + cd super && + git tag new_tag && + tree=$(git rev-parse HEAD^{tree}) && + commit=$(git rev-parse HEAD^{commit}) && + test-submodule-config $commit b >expect && + test-submodule-config $tree b >actual.1 && + test-submodule-config new_tag b >actual.2 && + test_cmp expect actual.1 && + test_cmp expect actual.2 + ) +' + cat >super/expect_url < Date: Tue, 22 Nov 2016 12:14:38 -0800 Subject: [PATCH 0009/1540] submodule-config: clarify parsing of null_sha1 element Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano Reviewed-by: Brandon Williams Signed-off-by: Junio C Hamano --- Documentation/technical/api-submodule-config.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/technical/api-submodule-config.txt b/Documentation/technical/api-submodule-config.txt index 8285bcc605ecfa..3dce003fda008e 100644 --- a/Documentation/technical/api-submodule-config.txt +++ b/Documentation/technical/api-submodule-config.txt @@ -56,8 +56,11 @@ Functions The same as above but lookup by name. -If given the null_sha1 as treeish_name the local configuration of a -submodule will be returned (e.g. consolidated values from local git +Whenever a submodule configuration is parsed in `parse_submodule_config_option` +via e.g. `gitmodules_config()`, it will overwrite the null_sha1 entry. +So in the normal case, when HEAD:.gitmodules is parsed first and then overlayed +with the repository configuration, the null_sha1 entry contains the local +configuration of a submodule (e.g. consolidated values from local git configuration and the .gitmodules file in the worktree). For an example usage see test-submodule-config.c. From b34fa5777d84abf123cd6b306e2a9a02dac4fc86 Mon Sep 17 00:00:00 2001 From: Vinicius Kursancew Date: Mon, 28 Nov 2016 09:33:18 +0000 Subject: [PATCH 0010/1540] git-p4: allow submit to create shelved changelists. Add a --shelve command line argument which invokes p4 shelve instead of submitting changes. After shelving the changes are reverted from the p4 workspace. Signed-off-by: Vinicius Kursancew Reviewed-by: Luke Diamand Signed-off-by: Junio C Hamano --- Documentation/git-p4.txt | 5 +++++ git-p4.py | 36 ++++++++++++++++++++++-------------- t/t9807-git-p4-submit.sh | 31 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index c83aaf39c33505..1bbf43d1572c7f 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -303,6 +303,11 @@ These options can be used to modify 'git p4 submit' behavior. submit manually or revert. This option always stops after the first (oldest) commit. Git tags are not exported to p4. +--shelve:: + Instead of submitting create a series of shelved changelists. + After creating each shelve, the relevant files are reverted/deleted. + If you have multiple commits pending multiple shelves will be created. + --conflict=(ask|skip|quit):: Conflicts can occur when applying a commit to p4. When this happens, the default behavior ("ask") is to prompt whether to diff --git a/git-p4.py b/git-p4.py index fd5ca524626c40..0c4f2afd238209 100755 --- a/git-p4.py +++ b/git-p4.py @@ -1289,6 +1289,9 @@ def __init__(self): optparse.make_option("--conflict", dest="conflict_behavior", choices=self.conflict_behavior_choices), optparse.make_option("--branch", dest="branch"), + optparse.make_option("--shelve", dest="shelve", action="store_true", + help="Shelve instead of submit. Shelved files are reverted, " + "restoring the workspace to the state before the shelve"), ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -1296,6 +1299,7 @@ def __init__(self): self.detectRenames = False self.preserveUser = gitConfigBool("git-p4.preserveUser") self.dry_run = False + self.shelve = False self.prepare_p4_only = False self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") @@ -1785,7 +1789,14 @@ def applyCommit(self, id): if self.isWindows: message = message.replace("\r\n", "\n") submitTemplate = message[:message.index(separatorLine)] - p4_write_pipe(['submit', '-i'], submitTemplate) + if self.shelve: + p4_write_pipe(['shelve', '-i'], submitTemplate) + else: + p4_write_pipe(['submit', '-i'], submitTemplate) + # The rename/copy happened by applying a patch that created a + # new file. This leaves it writable, which confuses p4. + for f in pureRenameCopy: + p4_sync(f, "-f") if self.preserveUser: if p4User: @@ -1795,23 +1806,20 @@ def applyCommit(self, id): changelist = self.lastP4Changelist() self.modifyChangelistUser(changelist, p4User) - # The rename/copy happened by applying a patch that created a - # new file. This leaves it writable, which confuses p4. - for f in pureRenameCopy: - p4_sync(f, "-f") submitted = True finally: # skip this patch - if not submitted: - print "Submission cancelled, undoing p4 changes." - for f in editedFiles: + if not submitted or self.shelve: + if self.shelve: + print ("Reverting shelved files.") + else: + print ("Submission cancelled, undoing p4 changes.") + for f in editedFiles | filesToDelete: p4_revert(f) for f in filesToAdd: p4_revert(f) os.remove(f) - for f in filesToDelete: - p4_revert(f) os.remove(fileName) return submitted @@ -2067,13 +2075,13 @@ def run(self, args): break chdir(self.oldWorkingDirectory) - + shelved_applied = "shelved" if self.shelve else "applied" if self.dry_run: pass elif self.prepare_p4_only: pass elif len(commits) == len(applied): - print "All commits applied!" + print ("All commits {0}!".format(shelved_applied)) sync = P4Sync() if self.branch: @@ -2085,9 +2093,9 @@ def run(self, args): else: if len(applied) == 0: - print "No commits applied." + print ("No commits {0}.".format(shelved_applied)) else: - print "Applied only the commits marked with '*':" + print ("{0} only the commits marked with '*':".format(shelved_applied.capitalize())) for c in commits: if c in applied: star = "*" diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 593152817dadaf..42a5fada58a9c7 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -413,6 +413,37 @@ test_expect_success 'submit --prepare-p4-only' ' ) ' +test_expect_success 'submit --shelve' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 revert ... && + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit "shelveme1" && + git p4 submit --origin=HEAD^ && + + echo 654321 >shelveme2.t && + echo 123456 >>shelveme1.t && + git add shelveme* && + git commit -m"shelvetest" && + git p4 submit --shelve --origin=HEAD^ && + + test_path_is_file shelveme1.t && + test_path_is_file shelveme2.t + ) && + ( + cd "$cli" && + change=$(p4 -G changes -s shelved -m 1 //depot/... | \ + marshal_dump change) && + p4 describe -S $change | grep shelveme2 && + p4 describe -S $change | grep 123456 && + test_path_is_file shelveme1.t && + test_path_is_missing shelveme2.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' From e4319562bc2834096fade432fd90c985b96476db Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Wed, 2 Nov 2016 10:29:16 -0700 Subject: [PATCH 0011/1540] trailer: be stricter in parsing separators Currently, a line is interpreted to be a trailer line if it contains a separator. Make parsing stricter by requiring the text on the left of the separator, if not the empty string, to be of the "" form. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/trailer.c b/trailer.c index f0ecde2d2f8b35..eefe91d86500a3 100644 --- a/trailer.c +++ b/trailer.c @@ -563,15 +563,32 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le } /* - * Return the location of the first separator in line, or -1 if there is no - * separator. + * If the given line is of the form + * "..." or "...", return the + * location of the separator. Otherwise, return -1. The optional whitespace + * is allowed there primarily to allow things like "Bug #43" where is + * "Bug" and is "#". + * + * The separator-starts-line case (in which this function returns 0) is + * distinguished from the non-well-formed-line case (in which this function + * returns -1) because some callers of this function need such a distinction. */ static int find_separator(const char *line, const char *separators) { - int loc = strcspn(line, separators); - if (!line[loc]) - return -1; - return loc; + int whitespace_found = 0; + const char *c; + for (c = line; *c; c++) { + if (strchr(separators, *c)) + return c - line; + if (!whitespace_found && (isalnum(*c) || *c == '-')) + continue; + if (c != line && (*c == ' ' || *c == '\t')) { + whitespace_found = 1; + continue; + } + break; + } + return -1; } /* From 710714aaa822acbad14f1b7100fa2776d2bc8ce6 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Wed, 2 Nov 2016 10:29:17 -0700 Subject: [PATCH 0012/1540] commit: make ignore_non_trailer take buf/len Make ignore_non_trailer take a buf/len pair instead of struct strbuf. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- builtin/commit.c | 2 +- commit.c | 22 +++++++++++----------- commit.h | 2 +- trailer.c | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index 8976c3d29bf817..887ccc7577a12a 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -790,7 +790,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_stripspace(&sb, 0); if (signoff) - append_signoff(&sb, ignore_non_trailer(&sb), 0); + append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0); if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); diff --git a/commit.c b/commit.c index 856fd4aeeff654..2cf85158b4899b 100644 --- a/commit.c +++ b/commit.c @@ -1649,7 +1649,7 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len } /* - * Inspect sb and determine the true "end" of the log message, in + * Inspect the given string and determine the true "end" of the log message, in * order to find where to put a new Signed-off-by: line. Ignored are * trailing comment lines and blank lines, and also the traditional * "Conflicts:" block that is not commented out, so that we can use @@ -1659,37 +1659,37 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len * Returns the number of bytes from the tail to ignore, to be fed as * the second parameter to append_signoff(). */ -int ignore_non_trailer(struct strbuf *sb) +int ignore_non_trailer(const char *buf, size_t len) { int boc = 0; int bol = 0; int in_old_conflicts_block = 0; - while (bol < sb->len) { - char *next_line; + while (bol < len) { + const char *next_line = memchr(buf + bol, '\n', len - bol); - if (!(next_line = memchr(sb->buf + bol, '\n', sb->len - bol))) - next_line = sb->buf + sb->len; + if (!next_line) + next_line = buf + len; else next_line++; - if (sb->buf[bol] == comment_line_char || sb->buf[bol] == '\n') { + if (buf[bol] == comment_line_char || buf[bol] == '\n') { /* is this the first of the run of comments? */ if (!boc) boc = bol; /* otherwise, it is just continuing */ - } else if (starts_with(sb->buf + bol, "Conflicts:\n")) { + } else if (starts_with(buf + bol, "Conflicts:\n")) { in_old_conflicts_block = 1; if (!boc) boc = bol; - } else if (in_old_conflicts_block && sb->buf[bol] == '\t') { + } else if (in_old_conflicts_block && buf[bol] == '\t') { ; /* a pathname in the conflicts block */ } else if (boc) { /* the previous was not trailing comment */ boc = 0; in_old_conflicts_block = 0; } - bol = next_line - sb->buf; + bol = next_line - buf; } - return boc ? sb->len - boc : 0; + return boc ? len - boc : 0; } diff --git a/commit.h b/commit.h index afd14f318c0c4d..9c12abb9111015 100644 --- a/commit.h +++ b/commit.h @@ -355,7 +355,7 @@ extern const char *find_commit_header(const char *msg, const char *key, size_t *out_len); /* Find the end of the log message, the right place for a new trailer. */ -extern int ignore_non_trailer(struct strbuf *sb); +extern int ignore_non_trailer(const char *buf, size_t len); typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra, void *cb_data); diff --git a/trailer.c b/trailer.c index eefe91d86500a3..9d101dbfbbe4f5 100644 --- a/trailer.c +++ b/trailer.c @@ -842,7 +842,7 @@ static int find_trailer_end(struct strbuf **lines, int patch_start) for (i = 0; i < patch_start; i++) strbuf_addbuf(&sb, lines[i]); - ignore_bytes = ignore_non_trailer(&sb); + ignore_bytes = ignore_non_trailer(sb.buf, sb.len); strbuf_release(&sb); for (i = patch_start - 1; i >= 0 && ignore_bytes > 0; i--) ignore_bytes -= lines[i]->len; From 022349c3b091f2aa047f1cd12b5409d564b25324 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Wed, 2 Nov 2016 10:29:18 -0700 Subject: [PATCH 0013/1540] trailer: avoid unnecessary splitting on lines trailer.c currently splits lines while processing a buffer (and also rejoins lines when needing to invoke ignore_non_trailer). Avoid such line splitting, except when generating the strings corresponding to trailers (for ease of use by clients - a subsequent patch will allow other components to obtain the layout of a trailer block in a buffer, including the trailers themselves). The main purpose of this is to make it easy to return pointers into the original buffer (for a subsequent patch), but this also significantly reduces the number of memory allocations required. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 194 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 100 insertions(+), 94 deletions(-) diff --git a/trailer.c b/trailer.c index 9d101dbfbbe4f5..2faccd7e2f803f 100644 --- a/trailer.c +++ b/trailer.c @@ -102,12 +102,12 @@ static int same_trailer(struct trailer_item *a, struct arg_item *b) return same_token(a, b) && same_value(a, b); } -static inline int contains_only_spaces(const char *str) +static inline int is_blank_line(const char *str) { const char *s = str; - while (*s && isspace(*s)) + while (*s && *s != '\n' && isspace(*s)) s++; - return !*s; + return !*s || *s == '\n'; } static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b) @@ -702,51 +702,71 @@ static void process_command_line_args(struct list_head *arg_head, free(cl_separators); } -static struct strbuf **read_input_file(const char *file) +static void read_input_file(struct strbuf *sb, const char *file) { - struct strbuf **lines; - struct strbuf sb = STRBUF_INIT; - if (file) { - if (strbuf_read_file(&sb, file, 0) < 0) + if (strbuf_read_file(sb, file, 0) < 0) die_errno(_("could not read input file '%s'"), file); } else { - if (strbuf_read(&sb, fileno(stdin), 0) < 0) + if (strbuf_read(sb, fileno(stdin), 0) < 0) die_errno(_("could not read from stdin")); } +} - lines = strbuf_split(&sb, '\n'); +static const char *next_line(const char *str) +{ + const char *nl = strchrnul(str, '\n'); + return nl + !!*nl; +} - strbuf_release(&sb); +/* + * Return the position of the start of the last line. If len is 0, return -1. + */ +static int last_line(const char *buf, size_t len) +{ + int i; + if (len == 0) + return -1; + if (len == 1) + return 0; + /* + * Skip the last character (in addition to the null terminator), + * because if the last character is a newline, it is considered as part + * of the last line anyway. + */ + i = len - 2; - return lines; + for (; i >= 0; i--) { + if (buf[i] == '\n') + return i + 1; + } + return 0; } /* - * Return the (0 based) index of the start of the patch or the line - * count if there is no patch in the message. + * Return the position of the start of the patch or the length of str if there + * is no patch in the message. */ -static int find_patch_start(struct strbuf **lines, int count) +static int find_patch_start(const char *str) { - int i; + const char *s; - /* Get the start of the patch part if any */ - for (i = 0; i < count; i++) { - if (starts_with(lines[i]->buf, "---")) - return i; + for (s = str; *s; s = next_line(s)) { + if (starts_with(s, "---")) + return s - str; } - return count; + return s - str; } /* - * Return the (0 based) index of the first trailer line or count if - * there are no trailers. Trailers are searched only in the lines from - * index (count - 1) down to index 0. + * Return the position of the first trailer line or len if there are no + * trailers. */ -static int find_trailer_start(struct strbuf **lines, int count) +static int find_trailer_start(const char *buf, size_t len) { - int start, end_of_title, only_spaces = 1; + const char *s; + int end_of_title, l, only_spaces = 1; int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; /* * Number of possible continuation lines encountered. This will be @@ -758,13 +778,13 @@ static int find_trailer_start(struct strbuf **lines, int count) int possible_continuation_lines = 0; /* The first paragraph is the title and cannot be trailers */ - for (start = 0; start < count; start++) { - if (lines[start]->buf[0] == comment_line_char) + for (s = buf; s < buf + len; s = next_line(s)) { + if (s[0] == comment_line_char) continue; - if (contains_only_spaces(lines[start]->buf)) + if (is_blank_line(s)) break; } - end_of_title = start; + end_of_title = s - buf; /* * Get the start of the trailers by looking starting from the end for a @@ -772,30 +792,33 @@ static int find_trailer_start(struct strbuf **lines, int count) * trailers, or (ii) contains at least one Git-generated trailer and * consists of at least 25% trailers. */ - for (start = count - 1; start >= end_of_title; start--) { + for (l = last_line(buf, len); + l >= end_of_title; + l = last_line(buf, l)) { + const char *bol = buf + l; const char **p; int separator_pos; - if (lines[start]->buf[0] == comment_line_char) { + if (bol[0] == comment_line_char) { non_trailer_lines += possible_continuation_lines; possible_continuation_lines = 0; continue; } - if (contains_only_spaces(lines[start]->buf)) { + if (is_blank_line(bol)) { if (only_spaces) continue; non_trailer_lines += possible_continuation_lines; if (recognized_prefix && trailer_lines * 3 >= non_trailer_lines) - return start + 1; - if (trailer_lines && !non_trailer_lines) - return start + 1; - return count; + return next_line(bol) - buf; + else if (trailer_lines && !non_trailer_lines) + return next_line(bol) - buf; + return len; } only_spaces = 0; for (p = git_generated_prefixes; *p; p++) { - if (starts_with(lines[start]->buf, *p)) { + if (starts_with(bol, *p)) { trailer_lines++; possible_continuation_lines = 0; recognized_prefix = 1; @@ -803,8 +826,8 @@ static int find_trailer_start(struct strbuf **lines, int count) } } - separator_pos = find_separator(lines[start]->buf, separators); - if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) { + separator_pos = find_separator(bol, separators); + if (separator_pos >= 1 && !isspace(bol[0])) { struct list_head *pos; trailer_lines++; @@ -814,13 +837,13 @@ static int find_trailer_start(struct strbuf **lines, int count) list_for_each(pos, &conf_head) { struct arg_item *item; item = list_entry(pos, struct arg_item, list); - if (token_matches_item(lines[start]->buf, item, + if (token_matches_item(bol, item, separator_pos)) { recognized_prefix = 1; break; } } - } else if (isspace(lines[start]->buf[0])) + } else if (isspace(bol[0])) possible_continuation_lines++; else { non_trailer_lines++; @@ -831,88 +854,70 @@ static int find_trailer_start(struct strbuf **lines, int count) ; } - return count; -} - -/* Get the index of the end of the trailers */ -static int find_trailer_end(struct strbuf **lines, int patch_start) -{ - struct strbuf sb = STRBUF_INIT; - int i, ignore_bytes; - - for (i = 0; i < patch_start; i++) - strbuf_addbuf(&sb, lines[i]); - ignore_bytes = ignore_non_trailer(sb.buf, sb.len); - strbuf_release(&sb); - for (i = patch_start - 1; i >= 0 && ignore_bytes > 0; i--) - ignore_bytes -= lines[i]->len; - - return i + 1; + return len; } -static int has_blank_line_before(struct strbuf **lines, int start) +/* Return the position of the end of the trailers. */ +static int find_trailer_end(const char *buf, size_t len) { - for (;start >= 0; start--) { - if (lines[start]->buf[0] == comment_line_char) - continue; - return contains_only_spaces(lines[start]->buf); - } - return 0; + return len - ignore_non_trailer(buf, len); } -static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end) +static int ends_with_blank_line(const char *buf, size_t len) { - int i; - for (i = start; lines[i] && i < end; i++) - fprintf(outfile, "%s", lines[i]->buf); + int ll = last_line(buf, len); + if (ll < 0) + return 0; + return is_blank_line(buf + ll); } static int process_input_file(FILE *outfile, - struct strbuf **lines, + const char *str, struct list_head *head) { - int count = 0; - int patch_start, trailer_start, trailer_end, i; + int patch_start, trailer_start, trailer_end; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; struct trailer_item *last = NULL; + struct strbuf *trailer, **trailer_lines, **ptr; - /* Get the line count */ - while (lines[count]) - count++; - - patch_start = find_patch_start(lines, count); - trailer_end = find_trailer_end(lines, patch_start); - trailer_start = find_trailer_start(lines, trailer_end); + patch_start = find_patch_start(str); + trailer_end = find_trailer_end(str, patch_start); + trailer_start = find_trailer_start(str, trailer_end); /* Print lines before the trailers as is */ - print_lines(outfile, lines, 0, trailer_start); + fwrite(str, 1, trailer_start, outfile); - if (!has_blank_line_before(lines, trailer_start - 1)) + if (!ends_with_blank_line(str, trailer_start)) fprintf(outfile, "\n"); /* Parse trailer lines */ - for (i = trailer_start; i < trailer_end; i++) { + trailer_lines = strbuf_split_buf(str + trailer_start, + trailer_end - trailer_start, + '\n', + 0); + for (ptr = trailer_lines; *ptr; ptr++) { int separator_pos; - if (lines[i]->buf[0] == comment_line_char) + trailer = *ptr; + if (trailer->buf[0] == comment_line_char) continue; - if (last && isspace(lines[i]->buf[0])) { + if (last && isspace(trailer->buf[0])) { struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf); + strbuf_addf(&sb, "%s\n%s", last->value, trailer->buf); strbuf_strip_suffix(&sb, "\n"); free(last->value); last->value = strbuf_detach(&sb, NULL); continue; } - separator_pos = find_separator(lines[i]->buf, separators); + separator_pos = find_separator(trailer->buf, separators); if (separator_pos >= 1) { - parse_trailer(&tok, &val, NULL, lines[i]->buf, + parse_trailer(&tok, &val, NULL, trailer->buf, separator_pos); last = add_trailer_item(head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL)); } else { - strbuf_addbuf(&val, lines[i]); + strbuf_addbuf(&val, trailer); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, NULL, @@ -920,6 +925,7 @@ static int process_input_file(FILE *outfile, last = NULL; } } + strbuf_list_free(trailer_lines); return trailer_end; } @@ -968,7 +974,7 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str { LIST_HEAD(head); LIST_HEAD(arg_head); - struct strbuf **lines; + struct strbuf sb = STRBUF_INIT; int trailer_end; FILE *outfile = stdout; @@ -976,13 +982,13 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str git_config(git_trailer_default_config, NULL); git_config(git_trailer_config, NULL); - lines = read_input_file(file); + read_input_file(&sb, file); if (in_place) outfile = create_in_place_tempfile(file); /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, lines, &head); + trailer_end = process_input_file(outfile, sb.buf, &head); process_command_line_args(&arg_head, trailers); @@ -993,11 +999,11 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str free_all(&head); /* Print the lines after the trailers as is */ - print_lines(outfile, lines, trailer_end, INT_MAX); + fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); if (in_place) if (rename_tempfile(&trailers_tempfile, file)) die_errno(_("could not rename temporary file to %s"), file); - strbuf_list_free(lines); + strbuf_release(&sb); } From e8c352c316f31a3869f3ad1dae0e8b33042cbaf4 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Wed, 2 Nov 2016 10:29:19 -0700 Subject: [PATCH 0014/1540] trailer: have function to describe trailer layout Create a function that, taking a string, describes the position of its trailer block (if available) and the contents thereof, and make trailer use it. This makes it easier for other Git components, in the future, to interpret trailer blocks in the same way as trailer. In a subsequent patch, another component will be made to use this. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 118 +++++++++++++++++++++++++++++++++++++----------------- trailer.h | 25 ++++++++++++ 2 files changed, 107 insertions(+), 36 deletions(-) diff --git a/trailer.c b/trailer.c index 2faccd7e2f803f..11f0b9fb40bced 100644 --- a/trailer.c +++ b/trailer.c @@ -46,6 +46,8 @@ static LIST_HEAD(conf_head); static char *separators = ":"; +static int configured; + #define TRAILER_ARG_STRING "$ARG" static const char *git_generated_prefixes[] = { @@ -546,6 +548,17 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb) return 0; } +static void ensure_configured(void) +{ + if (configured) + return; + + /* Default config must be setup first */ + git_config(git_trailer_default_config, NULL); + git_config(git_trailer_config, NULL); + configured = 1; +} + static const char *token_from_item(struct arg_item *item, char *tok) { if (item->conf.key) @@ -875,59 +888,43 @@ static int process_input_file(FILE *outfile, const char *str, struct list_head *head) { - int patch_start, trailer_start, trailer_end; + struct trailer_info info; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; - struct trailer_item *last = NULL; - struct strbuf *trailer, **trailer_lines, **ptr; + int i; - patch_start = find_patch_start(str); - trailer_end = find_trailer_end(str, patch_start); - trailer_start = find_trailer_start(str, trailer_end); + trailer_info_get(&info, str); /* Print lines before the trailers as is */ - fwrite(str, 1, trailer_start, outfile); + fwrite(str, 1, info.trailer_start - str, outfile); - if (!ends_with_blank_line(str, trailer_start)) + if (!info.blank_line_before_trailer) fprintf(outfile, "\n"); - /* Parse trailer lines */ - trailer_lines = strbuf_split_buf(str + trailer_start, - trailer_end - trailer_start, - '\n', - 0); - for (ptr = trailer_lines; *ptr; ptr++) { + for (i = 0; i < info.trailer_nr; i++) { int separator_pos; - trailer = *ptr; - if (trailer->buf[0] == comment_line_char) - continue; - if (last && isspace(trailer->buf[0])) { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%s\n%s", last->value, trailer->buf); - strbuf_strip_suffix(&sb, "\n"); - free(last->value); - last->value = strbuf_detach(&sb, NULL); + char *trailer = info.trailers[i]; + if (trailer[0] == comment_line_char) continue; - } - separator_pos = find_separator(trailer->buf, separators); + separator_pos = find_separator(trailer, separators); if (separator_pos >= 1) { - parse_trailer(&tok, &val, NULL, trailer->buf, + parse_trailer(&tok, &val, NULL, trailer, separator_pos); - last = add_trailer_item(head, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + add_trailer_item(head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); } else { - strbuf_addbuf(&val, trailer); + strbuf_addstr(&val, trailer); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, NULL, strbuf_detach(&val, NULL)); - last = NULL; } } - strbuf_list_free(trailer_lines); - return trailer_end; + trailer_info_release(&info); + + return info.trailer_end - str; } static void free_all(struct list_head *head) @@ -978,9 +975,7 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str int trailer_end; FILE *outfile = stdout; - /* Default config must be setup first */ - git_config(git_trailer_default_config, NULL); - git_config(git_trailer_config, NULL); + ensure_configured(); read_input_file(&sb, file); @@ -1007,3 +1002,54 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str strbuf_release(&sb); } + +void trailer_info_get(struct trailer_info *info, const char *str) +{ + int patch_start, trailer_end, trailer_start; + struct strbuf **trailer_lines, **ptr; + char **trailer_strings = NULL; + size_t nr = 0, alloc = 0; + char **last = NULL; + + ensure_configured(); + + patch_start = find_patch_start(str); + trailer_end = find_trailer_end(str, patch_start); + trailer_start = find_trailer_start(str, trailer_end); + + trailer_lines = strbuf_split_buf(str + trailer_start, + trailer_end - trailer_start, + '\n', + 0); + for (ptr = trailer_lines; *ptr; ptr++) { + if (last && isspace((*ptr)->buf[0])) { + struct strbuf sb = STRBUF_INIT; + strbuf_attach(&sb, *last, strlen(*last), strlen(*last)); + strbuf_addbuf(&sb, *ptr); + *last = strbuf_detach(&sb, NULL); + continue; + } + ALLOC_GROW(trailer_strings, nr + 1, alloc); + trailer_strings[nr] = strbuf_detach(*ptr, NULL); + last = find_separator(trailer_strings[nr], separators) >= 1 + ? &trailer_strings[nr] + : NULL; + nr++; + } + strbuf_list_free(trailer_lines); + + info->blank_line_before_trailer = ends_with_blank_line(str, + trailer_start); + info->trailer_start = str + trailer_start; + info->trailer_end = str + trailer_end; + info->trailers = trailer_strings; + info->trailer_nr = nr; +} + +void trailer_info_release(struct trailer_info *info) +{ + int i; + for (i = 0; i < info->trailer_nr; i++) + free(info->trailers[i]); + free(info->trailers); +} diff --git a/trailer.h b/trailer.h index 36b40b81761f95..65cc5d79c6cecf 100644 --- a/trailer.h +++ b/trailer.h @@ -1,7 +1,32 @@ #ifndef TRAILER_H #define TRAILER_H +struct trailer_info { + /* + * True if there is a blank line before the location pointed to by + * trailer_start. + */ + int blank_line_before_trailer; + + /* + * Pointers to the start and end of the trailer block found. If there + * is no trailer block found, these 2 pointers point to the end of the + * input string. + */ + const char *trailer_start, *trailer_end; + + /* + * Array of trailers found. + */ + char **trailers; + size_t trailer_nr; +}; + void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers); +void trailer_info_get(struct trailer_info *info, const char *str); + +void trailer_info_release(struct trailer_info *info); + #endif /* TRAILER_H */ From 967dfd4d568c2b102281de8cc22ee35f7558358b Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Wed, 2 Nov 2016 10:29:20 -0700 Subject: [PATCH 0015/1540] sequencer: use trailer's trailer layout Make sequencer use trailer.c's trailer layout definition, as opposed to parsing the footer by itself. This makes "commit -s", "cherry-pick -x", and "format-patch --signoff" consistent with trailer, allowing non-trailer lines and multiple-line trailers in trailer blocks under certain conditions, and therefore suppressing the extra newline in those cases. Consistency with trailer extends to respecting trailer configs. Tests have been included to show that. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- sequencer.c | 75 ++++++++-------------------------------- t/t3511-cherry-pick-x.sh | 16 +++++++-- t/t4014-format-patch.sh | 37 +++++++++++++++++--- t/t7501-commit.sh | 36 +++++++++++++++++++ 4 files changed, 95 insertions(+), 69 deletions(-) diff --git a/sequencer.c b/sequencer.c index 5fd75f30dd4455..d64c97397b5387 100644 --- a/sequencer.c +++ b/sequencer.c @@ -16,6 +16,7 @@ #include "refs.h" #include "argv-array.h" #include "quote.h" +#include "trailer.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -56,30 +57,6 @@ static const char *get_todo_path(const struct replay_opts *opts) return git_path_todo_file(); } -static int is_rfc2822_line(const char *buf, int len) -{ - int i; - - for (i = 0; i < len; i++) { - int ch = buf[i]; - if (ch == ':') - return 1; - if (!isalnum(ch) && ch != '-') - break; - } - - return 0; -} - -static int is_cherry_picked_from_line(const char *buf, int len) -{ - /* - * We only care that it looks roughly like (cherry picked from ...) - */ - return len > strlen(cherry_picked_prefix) + 1 && - starts_with(buf, cherry_picked_prefix) && buf[len - 1] == ')'; -} - /* * Returns 0 for non-conforming footer * Returns 1 for conforming footer @@ -89,49 +66,25 @@ static int is_cherry_picked_from_line(const char *buf, int len) static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, int ignore_footer) { - char prev; - int i, k; - int len = sb->len - ignore_footer; - const char *buf = sb->buf; - int found_sob = 0; - - /* footer must end with newline */ - if (!len || buf[len - 1] != '\n') - return 0; + struct trailer_info info; + int i; + int found_sob = 0, found_sob_last = 0; - prev = '\0'; - for (i = len - 1; i > 0; i--) { - char ch = buf[i]; - if (prev == '\n' && ch == '\n') /* paragraph break */ - break; - prev = ch; - } + trailer_info_get(&info, sb->buf); - /* require at least one blank line */ - if (prev != '\n' || buf[i] != '\n') + if (info.trailer_start == info.trailer_end) return 0; - /* advance to start of last paragraph */ - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - int found_rfc2822; - - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; + for (i = 0; i < info.trailer_nr; i++) + if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) { + found_sob = 1; + if (i == info.trailer_nr - 1) + found_sob_last = 1; + } - found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1); - if (found_rfc2822 && sob && - !strncmp(buf + i, sob->buf, sob->len)) - found_sob = k; + trailer_info_release(&info); - if (!(found_rfc2822 || - is_cherry_picked_from_line(buf + i, k - i - 1))) - return 0; - } - if (found_sob == i) + if (found_sob_last) return 3; if (found_sob) return 2; diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh index 9cce5ae8815a11..bf0a5c9887235a 100755 --- a/t/t3511-cherry-pick-x.sh +++ b/t/t3511-cherry-pick-x.sh @@ -25,9 +25,8 @@ Signed-off-by: B.U. Thor " mesg_broken_footer="$mesg_no_footer -The signed-off-by string should begin with the words Signed-off-by followed -by a colon and space, and then the signers name and email address. e.g. -Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +This is not recognized as a footer because Myfooter is not a recognized token. +Myfooter: A.U. Thor " mesg_with_footer_sob="$mesg_with_footer Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" @@ -112,6 +111,17 @@ test_expect_success 'cherry-pick -s inserts blank line after non-conforming foot test_cmp expect actual ' +test_expect_success 'cherry-pick -s recognizes trailer config' ' + pristine_detach initial && + git -c "trailer.Myfooter.ifexists=add" cherry-pick -s mesg-broken-footer && + cat <<-EOF >expect && + $mesg_broken_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' ' pristine_detach initial && sha1=$(git rev-parse mesg-no-footer^0) && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index ba4902df2b605f..482112ca339f05 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1294,8 +1294,7 @@ EOF 4:Subject: [PATCH] subject 8: 10:Signed-off-by: example happens to be wrapped here. -11: -12:Signed-off-by: C O Mitter +11:Signed-off-by: C O Mitter EOF test_cmp expected actual ' @@ -1368,7 +1367,7 @@ EOF test_cmp expected actual ' -test_expect_success 'signoff: detect garbage in non-conforming footer' ' +test_expect_success 'signoff: tolerate garbage in conforming footer' ' append_signoff <<\EOF >actual && subject @@ -1383,8 +1382,36 @@ EOF 8: 10: 13:Signed-off-by: C O Mitter -14: -15:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: respect trailer config' ' + append_signoff <<\EOF >actual && +subject + +Myfooter: x +Some Trash +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11: +12:Signed-off-by: C O Mitter +EOF + test_cmp expected actual && + + test_config trailer.Myfooter.ifexists add && + append_signoff <<\EOF >actual && +subject + +Myfooter: x +Some Trash +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11:Signed-off-by: C O Mitter EOF test_cmp expected actual ' diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index d84897a67a3c36..4003a27e6aa117 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -460,6 +460,42 @@ $alt" && test_cmp expected actual ' +test_expect_success 'signoff respects trailer config' ' + + echo 5 >positive && + git add positive && + git commit -s -m "subject + +non-trailer line +Myfooter: x" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo subject + echo + echo non-trailer line + echo Myfooter: x + echo + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expected && + test_cmp expected actual && + + echo 6 >positive && + git add positive && + git -c "trailer.Myfooter.ifexists=add" commit -s -m "subject + +non-trailer line +Myfooter: x" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo subject + echo + echo non-trailer line + echo Myfooter: x + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expected && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && From 619acfc78c526bc9df17b7704e60d0512ab7a84c Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Thu, 6 Oct 2016 12:37:24 -0700 Subject: [PATCH 0016/1540] submodule add: extend force flag to add existing repos Currently the force flag in `git submodule add` takes care of possibly ignored files or when a name collision occurs. However there is another situation where submodule add comes in handy: When you already have a gitlink recorded, but no configuration was done (i.e. no .gitmodules file nor any entry in .git/config) and you want to generate these config entries. For this situation allow `git submodule add` to proceed if there is already a submodule at the given path in the index. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- git-submodule.sh | 10 ++++++++-- t/t7400-submodule-basic.sh | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/git-submodule.sh b/git-submodule.sh index a024a135d6663c..3762616b5cf2cb 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -207,8 +207,14 @@ cmd_add() tstart s|/*$|| ') - git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && - die "$(eval_gettext "'\$sm_path' already exists in the index")" + if test -z "$force" + then + git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index")" + else + git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")" + fi if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1 then diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index b77cce8e4066ac..c09ce0d4c1dbd8 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -152,6 +152,20 @@ test_expect_success 'submodule add to .gitignored path with --force' ' ) ' +test_expect_success 'submodule add to reconfigure existing submodule with --force' ' + ( + cd addtest-ignore && + git submodule add --force bogus-url submod && + git submodule add -b initial "$submodurl" submod-branch && + test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" && + test "bogus-url" = "$(git config submodule.submod.url)" && + # Restore the url + git submodule add --force "$submodurl" submod + test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" && + test "$submodurl" = "$(git config submodule.submod.url)" + ) +' + test_expect_success 'submodule add --branch' ' echo "refs/heads/initial" >expect-head && cat <<-\EOF >expect-heads && From 8d7a455ed52e2a96debc080dfc011b6bb00db5d2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Dec 2016 11:31:47 -0800 Subject: [PATCH 0017/1540] Start post 2.11 cycle For now, let's call it 2.12 tentatively. Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 43 +++++++++++++++++++++++++++++++ GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Documentation/RelNotes/2.12.0.txt diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt new file mode 100644 index 00000000000000..08152d64b93917 --- /dev/null +++ b/Documentation/RelNotes/2.12.0.txt @@ -0,0 +1,43 @@ +Git 2.12 Release Notes +====================== + +Backward compatibility notes. + + * Use of an empty string that is used for 'everything matches' is + still warned and Git asks users to use a more explicit '.' for that + instead. The hope is that existing users will not mind this + change, and eventually the warning can be turned into a hard error, + upgrading the deprecation into removal of this (mis)feature. That + is not scheduled to happen in the upcoming release (yet). + + * The historical argument order "git merge HEAD ..." + has been deprecated for quite some time, and will be removed in the + upcoming release. + + +Updates since v2.11 +------------------- + +UI, Workflows & Features + + * + + +Performance, Internal Implementation, Development Support etc. + + * + + +Also contains various documentation updates and code clean-ups. + + * + + +Fixes since v2.10 +----------------- + +Unless otherwise noted, all the fixes since v2.9 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * Other minor doc, test and build updates and code cleanups. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 520d6e66ec9f14..556fbfc104b1ed 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.11.0 +DEF_VER=v2.11.GIT LF=' ' diff --git a/RelNotes b/RelNotes index b54330f7cdb376..d09c3d51093ac9 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.11.0.txt \ No newline at end of file +Documentation/RelNotes/2.12.0.txt \ No newline at end of file From 46c609e9ffdbf8d9aba096836386b57371ed16b8 Mon Sep 17 00:00:00 2001 From: Luke Diamand Date: Fri, 2 Dec 2016 22:43:19 +0000 Subject: [PATCH 0018/1540] git-p4: support updating an existing shelved changelist Adds new option "--update-shelve CHANGELIST" which updates an existing shelved changelist. The original changelist must have been created by the current user. This allows workflow something like: hack hack hack git commit git p4 submit --shelve $mail interested parties about shelved changelist make corrections git commit --amend git p4 submit --update-shelve $CHANGELIST $mail interested parties about shelved changelist etc Signed-off-by: Luke Diamand Signed-off-by: Junio C Hamano --- Documentation/git-p4.txt | 4 ++++ git-p4.py | 33 +++++++++++++++++++++++++++++---- t/t9807-git-p4-submit.sh | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 1bbf43d1572c7f..ce40b9a5475307 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -308,6 +308,10 @@ These options can be used to modify 'git p4 submit' behavior. After creating each shelve, the relevant files are reverted/deleted. If you have multiple commits pending multiple shelves will be created. +--update-shelve CHANGELIST:: + Update an existing shelved changelist with this commit. Implies + --shelve. + --conflict=(ask|skip|quit):: Conflicts can occur when applying a commit to p4. When this happens, the default behavior ("ask") is to prompt whether to diff --git a/git-p4.py b/git-p4.py index 0c4f2afd238209..bc0fcdf90a5a79 100755 --- a/git-p4.py +++ b/git-p4.py @@ -262,6 +262,10 @@ def p4_revert(f): def p4_reopen(type, f): p4_system(["reopen", "-t", type, wildcard_encode(f)]) +def p4_reopen_in_change(changelist, files): + cmd = ["reopen", "-c", str(changelist)] + files + p4_system(cmd) + def p4_move(src, dest): p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)]) @@ -1292,6 +1296,9 @@ def __init__(self): optparse.make_option("--shelve", dest="shelve", action="store_true", help="Shelve instead of submit. Shelved files are reverted, " "restoring the workspace to the state before the shelve"), + optparse.make_option("--update-shelve", dest="update_shelve", action="store", type="int", + metavar="CHANGELIST", + help="update an existing shelved changelist, implies --shelve") ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -1300,6 +1307,7 @@ def __init__(self): self.preserveUser = gitConfigBool("git-p4.preserveUser") self.dry_run = False self.shelve = False + self.update_shelve = None self.prepare_p4_only = False self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") @@ -1468,7 +1476,7 @@ def canChangeChangelists(self): return 1 return 0 - def prepareSubmitTemplate(self): + def prepareSubmitTemplate(self, changelist=None): """Run "p4 change -o" to grab a change specification template. This does not use "p4 -G", as it is nice to keep the submission template in original order, since a human might edit it. @@ -1480,7 +1488,11 @@ def prepareSubmitTemplate(self): template = "" inFilesSection = False - for line in p4_read_pipe_lines(['change', '-o']): + args = ['change', '-o'] + if changelist: + args.append(str(changelist)) + + for line in p4_read_pipe_lines(args): if line.endswith("\r\n"): line = line[:-2] + "\n" if inFilesSection: @@ -1579,11 +1591,14 @@ def applyCommit(self, id): editedFiles = set() pureRenameCopy = set() filesToChangeExecBit = {} + all_files = list() for line in diff: diff = parseDiffTreeEntry(line) modifier = diff['status'] path = diff['src'] + all_files.append(path) + if modifier == "M": p4_edit(path) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): @@ -1709,6 +1724,10 @@ def applyCommit(self, id): mode = filesToChangeExecBit[f] setP4ExecBit(f, mode) + if self.update_shelve: + print("all_files = %s" % str(all_files)) + p4_reopen_in_change(self.update_shelve, all_files) + # # Build p4 change description, starting with the contents # of the git commit message. @@ -1717,7 +1736,7 @@ def applyCommit(self, id): logMessage = logMessage.strip() (logMessage, jobs) = self.separate_jobs_from_description(logMessage) - template = self.prepareSubmitTemplate() + template = self.prepareSubmitTemplate(self.update_shelve) submitTemplate = self.prepareLogMessage(template, logMessage, jobs) if self.preserveUser: @@ -1789,7 +1808,10 @@ def applyCommit(self, id): if self.isWindows: message = message.replace("\r\n", "\n") submitTemplate = message[:message.index(separatorLine)] - if self.shelve: + + if self.update_shelve: + p4_write_pipe(['shelve', '-r', '-i'], submitTemplate) + elif self.shelve: p4_write_pipe(['shelve', '-i'], submitTemplate) else: p4_write_pipe(['submit', '-i'], submitTemplate) @@ -1915,6 +1937,9 @@ def run(self, args): if len(self.origin) == 0: self.origin = upstream + if self.update_shelve: + self.shelve = True + if self.preserveUser: if not self.canChangeChangelists(): die("Cannot preserve user names without p4 super-user or admin permissions") diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 42a5fada58a9c7..e37239e657964e 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -444,6 +444,44 @@ test_expect_success 'submit --shelve' ' ) ' +# Update an existing shelved changelist + +test_expect_success 'submit --update-shelve' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 revert ... && + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit "test-update-shelved-change" && + git p4 submit --origin=HEAD^ --shelve && + + shelf_cl=$(p4 -G changes -s shelved -m 1 |\ + marshal_dump change) && + test -n $shelf_cl && + echo "updating shelved change list $shelf_cl" && + + echo "updated-line" >>shelf.t && + echo added-file.t >added-file.t && + git add shelf.t added-file.t && + git rm -f test-update-shelved-change.t && + git commit --amend -C HEAD && + git show --stat HEAD && + git p4 submit -v --origin HEAD^ --update-shelve $shelf_cl && + echo "done git p4 submit" + ) && + ( + cd "$cli" && + change=$(p4 -G changes -s shelved -m 1 //depot/... | \ + marshal_dump change) && + p4 unshelve -c $change -s $change && + grep -q updated-line shelf.t && + p4 describe -S $change | grep added-file.t && + test_path_is_missing test-update-shelved-change.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' From 3f407b7614bccffffad14fad42377dd0c3603f66 Mon Sep 17 00:00:00 2001 From: Alex Henrie Date: Sun, 4 Dec 2016 15:04:23 -0700 Subject: [PATCH 0019/1540] bisect: improve English grammar of not-ancestors message Multiple revisions cannot be a single ancestor. Signed-off-by: Alex Henrie Signed-off-by: Junio C Hamano --- bisect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bisect.c b/bisect.c index 21bc6daa4393cb..8e63c40d274d76 100644 --- a/bisect.c +++ b/bisect.c @@ -747,7 +747,7 @@ static void handle_bad_merge_base(void) exit(3); } - fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n" + fprintf(stderr, _("Some %s revs are not ancestors of the %s rev.\n" "git bisect cannot work properly in this case.\n" "Maybe you mistook %s and %s revs?\n"), term_good, term_bad, term_good, term_bad); From 2ddaa42783dcead77b840d5a26f20ba7d7ca3ec8 Mon Sep 17 00:00:00 2001 From: Alex Henrie Date: Sun, 4 Dec 2016 15:04:40 -0700 Subject: [PATCH 0020/1540] receive-pack: improve English grammar of denyCurrentBranch message The article "the" is required here. Signed-off-by: Alex Henrie 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 e6b3879a5b9003..6b97cbdbe9444d 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -795,8 +795,8 @@ static char *refuse_unconfigured_deny_msg = "with what you pushed, and will require 'git reset --hard' to match\n" "the work tree to HEAD.\n" "\n" - "You can set 'receive.denyCurrentBranch' configuration variable to\n" - "'ignore' or 'warn' in the remote repository to allow pushing into\n" + "You can set the 'receive.denyCurrentBranch' configuration variable\n" + "to 'ignore' or 'warn' in the remote repository to allow pushing into\n" "its current branch; however, this is not recommended unless you\n" "arranged to update its work tree to match what you pushed in some\n" "other way.\n" From 6d8738653297e839ae59ff3284c271681cc14396 Mon Sep 17 00:00:00 2001 From: Alex Henrie Date: Sun, 4 Dec 2016 15:03:59 -0700 Subject: [PATCH 0021/1540] clone,fetch: explain the shallow-clone option a little more clearly "deepen by excluding" does not make sense because excluding a revision does not deepen a repository; it makes the repository more shallow. Signed-off-by: Alex Henrie Signed-off-by: Junio C Hamano --- builtin/clone.c | 2 +- builtin/fetch.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 6c76a6ed66fef5..e3cb8082f7e83b 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -99,7 +99,7 @@ static struct option builtin_clone_options[] = { OPT_STRING(0, "shallow-since", &option_since, N_("time"), N_("create a shallow clone since a specific time")), OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"), - N_("deepen history of shallow clone by excluding rev")), + N_("deepen history of shallow clone, excluding rev")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules, diff --git a/builtin/fetch.c b/builtin/fetch.c index b6a5597cbf332f..fc74c8471cec5d 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -122,7 +122,7 @@ static struct option builtin_fetch_options[] = { OPT_STRING(0, "shallow-since", &deepen_since, N_("time"), N_("deepen history of shallow repository based on time")), OPT_STRING_LIST(0, "shallow-exclude", &deepen_not, N_("revision"), - N_("deepen history of shallow clone by excluding rev")), + N_("deepen history of shallow clone, excluding rev")), OPT_INTEGER(0, "deepen", &deepen_relative, N_("deepen history of shallow clone")), { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL, From 89a6ecc55b55ae87c39c5d9edb115731550fba95 Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Sun, 4 Dec 2016 15:03:11 +0100 Subject: [PATCH 0022/1540] git-p4: add config to retry p4 commands; retry 3 times by default P4 commands can fail due to random network issues. P4 users can counter these issues by using a retry flag supported by all p4 commands [1]. Add an integer Git config value `git-p4.retries` to define the number of retries for all p4 invocations. If the config is not defined then set the default retry count to 3. [1] https://www.perforce.com/perforce/doc.current/manuals/cmdref/global.options.html Signed-off-by: Lars Schneider Reviewed-by: Luke Diamand Signed-off-by: Junio C Hamano --- Documentation/git-p4.txt | 4 ++++ git-p4.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index c83aaf39c33505..656587248cc466 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -467,6 +467,10 @@ git-p4.client:: Client specified as an option to all p4 commands, with '-c ', including the client spec. +git-p4.retries:: + Specifies the number of times to retry a p4 command (notably, + 'p4 sync') if the network times out. The default value is 3. + Clone and sync variables ~~~~~~~~~~~~~~~~~~~~~~~~ git-p4.syncFromOrigin:: diff --git a/git-p4.py b/git-p4.py index fd5ca524626c40..2422178210f344 100755 --- a/git-p4.py +++ b/git-p4.py @@ -78,6 +78,11 @@ def p4_build_cmd(cmd): if len(client) > 0: real_cmd += ["-c", client] + retries = gitConfigInt("git-p4.retries") + if retries is None: + # Perform 3 retries by default + retries = 3 + real_cmd += ["-r", str(retries)] if isinstance(cmd,basestring): real_cmd = ' '.join(real_cmd) + ' ' + cmd From 3bb16a8bf2ec02c4cc633c3efd4c012e55ee0c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 4 Dec 2016 09:52:25 +0700 Subject: [PATCH 0023/1540] tag, branch, for-each-ref: add --ignore-case for sorting and filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This options makes sorting ignore case, which is great when you have branches named bug-12-do-something, Bug-12-do-some-more and BUG-12-do-what and want to group them together. Sorting externally may not be an option because we lose coloring and column layout from git-branch and git-tag. The same could be said for filtering, but it's probably less important because you can always go with the ugly pattern [bB][uU][gG]-* if you're desperate. You can't have case-sensitive filtering and case-insensitive sorting (or the other way around) with this though. For branch and tag, that should be no problem. for-each-ref, as a plumbing, might want finer control. But we can always add --{filter,sort}-ignore-case when there is a need for it. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 4 ++++ Documentation/git-for-each-ref.txt | 3 +++ Documentation/git-tag.txt | 4 ++++ builtin/branch.c | 23 ++++++++++++++--------- builtin/for-each-ref.c | 5 ++++- builtin/tag.c | 4 ++++ ref-filter.c | 28 +++++++++++++++++++++------- ref-filter.h | 2 ++ t/t3203-branch-output.sh | 29 +++++++++++++++++++++++++++++ t/t7004-tag.sh | 27 +++++++++++++++++++++++++++ 10 files changed, 112 insertions(+), 17 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 1fe73448f3f5a3..5516a47b5490ff 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -118,6 +118,10 @@ OPTIONS default to color output. Same as `--color=never`. +-i:: +--ignore-case:: + Sorting and filtering branches are case insensitive. + --column[=]:: --no-column:: Display branch listing in columns. See configuration variable diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index f57e69bc83e33e..6d22974da6e8fe 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -79,6 +79,9 @@ OPTIONS Only list refs which contain the specified commit (HEAD if not specified). +--ignore-case:: + Sorting and filtering refs are case insensitive. + FIELD NAMES ----------- diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 80019c584b11b3..76cfe40d969bc5 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -108,6 +108,10 @@ OPTIONS variable if it exists, or lexicographic order otherwise. See linkgit:git-config[1]. +-i:: +--ignore-case:: + Sorting and filtering tags are case insensitive. + --column[=]:: --no-column:: Display tag listing in columns. See configuration variable diff --git a/builtin/branch.c b/builtin/branch.c index 60cc5c8e8da08e..36e0a21af5da61 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - /* - * 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++) @@ -645,6 +636,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; + int icase = 0; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { @@ -686,6 +678,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only branches of the object"), 0, parse_opt_object_name }, + OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END(), }; @@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (filter.abbrev == -1) filter.abbrev = DEFAULT_ABBREV; + filter.ignore_case = icase; + finalize_colopts(&colopts, -1); if (filter.verbose) { if (explicitly_enable_column(colopts)) @@ -744,6 +739,16 @@ 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; + /* + * 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(); + sorting->ignore_case = icase; print_ref_list(&filter, sorting); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 4e9f6c29bf1e0c..df41fa035004e1 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) int i; const char *format = "%(objectname) %(objecttype)\t%(refname)"; struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - int maxcount = 0, quote_style = 0; + int maxcount = 0, quote_style = 0, icase = 0; struct ref_array array; struct ref_filter filter; @@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) 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_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END(), }; @@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); + sorting->ignore_case = icase; + filter.ignore_case = icase; /* for warn_ambiguous_refs */ git_config(git_default_config, NULL); diff --git a/builtin/tag.c b/builtin/tag.c index 50e4ae5678c21f..73df728114e81a 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -335,6 +335,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct ref_filter filter; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; const char *format = NULL; + int icase = 0; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), @@ -370,6 +371,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END() }; @@ -401,6 +403,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (!sorting) sorting = ref_default_sorting(); + sorting->ignore_case = icase; + filter.ignore_case = icase; if (cmdmode == 'l') { int ret; if (column_active(colopts)) { diff --git a/ref-filter.c b/ref-filter.c index f5f7a70c6d9b8e..bd9801024c9cc9 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit) * 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) +static int match_pattern(const struct ref_filter *filter, const char *refname) { + const char **patterns = filter->name_patterns; + unsigned flags = 0; + + if (filter->ignore_case) + flags |= WM_CASEFOLD; + /* * When no '--format' option is given we need to skip the prefix * for matching refs of tags and branches. @@ -1243,7 +1249,7 @@ static int match_pattern(const char **patterns, const char *refname) skip_prefix(refname, "refs/", &refname)); for (; *patterns; patterns++) { - if (!wildmatch(*patterns, refname, 0, NULL)) + if (!wildmatch(*patterns, refname, flags, NULL)) return 1; } return 0; @@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname) * 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) +static int match_name_as_path(const struct ref_filter *filter, const char *refname) { + const char **pattern = filter->name_patterns; int namelen = strlen(refname); + unsigned flags = WM_PATHNAME; + + if (filter->ignore_case) + flags |= WM_CASEFOLD; + for (; *pattern; pattern++) { const char *p = *pattern; int plen = strlen(p); @@ -1280,8 +1292,8 @@ 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); + return match_name_as_path(filter, refname); + return match_pattern(filter, refname); } /* @@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru struct atom_value *va, *vb; int cmp; cmp_type cmp_type = used_atom[s->atom].type; + int (*cmp_fn)(const char *, const char *); get_ref_atom_value(a, s->atom, &va); get_ref_atom_value(b, s->atom, &vb); + cmp_fn = s->ignore_case ? strcasecmp : strcmp; if (s->version) cmp = versioncmp(va->s, vb->s); else if (cmp_type == FIELD_STR) - cmp = strcmp(va->s, vb->s); + cmp = cmp_fn(va->s, vb->s); else { if (va->ul < vb->ul) cmp = -1; else if (va->ul == vb->ul) - cmp = strcmp(a->refname, b->refname); + cmp = cmp_fn(a->refname, b->refname); else cmp = 1; } diff --git a/ref-filter.h b/ref-filter.h index 14d435e2ccf020..fc55fa3574620b 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -29,6 +29,7 @@ struct ref_sorting { struct ref_sorting *next; int atom; /* index into used_atom array (internal) */ unsigned reverse : 1, + ignore_case : 1, version : 1; }; @@ -62,6 +63,7 @@ struct ref_filter { unsigned int with_commit_tag_algo : 1, match_as_path : 1, + ignore_case : 1, detached : 1; unsigned int kind, lines; diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index c6a3ccba1b992c..52283dfc8cdafa 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' ' awk "{print \$NF}" actual && test_cmp expect actual ' +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' ' + git branch --list --ignore-case -v BRANCH* >tmp && + awk "{print \$NF}" actual && + test_cmp expect actual +' test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch* @@ -196,4 +201,28 @@ test_expect_success 'local-branch symrefs shortened properly' ' test_cmp expect actual ' +test_expect_success 'sort branches, ignore case' ' + ( + git init sort-icase && + cd sort-icase && + test_commit initial && + git branch branch-one && + git branch BRANCH-two && + git branch --list | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + BRANCH-two + branch-one + master + EOF + test_cmp expected actual && + git branch --list -i | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + branch-one + BRANCH-two + master + EOF + test_cmp expected actual + ) +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 8b0f71a2ac15d6..07869b0c09da13 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -27,6 +27,30 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' test $(git tag | wc -l) -eq 0 ' +test_expect_success 'sort tags, ignore case' ' + ( + git init sort && + cd sort && + test_commit initial && + git tag tag-one && + git tag TAG-two && + git tag -l >actual && + cat >expected <<-\EOF && + TAG-two + initial + tag-one + EOF + test_cmp expected actual && + git tag -l -i >actual && + cat >expected <<-\EOF && + initial + tag-one + TAG-two + EOF + test_cmp expected actual + ) +' + test_expect_success 'looking for a tag in an empty tree should fail' \ '! (tag_exists mytag)' @@ -81,6 +105,9 @@ test_expect_success 'listing all tags if one exists should output that tag' ' test_expect_success 'listing a tag using a matching pattern should succeed' \ 'git tag -l mytag' +test_expect_success 'listing a tag with --ignore-case' \ + 'test $(git tag -l --ignore-case MYTAG) = mytag' + test_expect_success \ 'listing a tag using a matching pattern should output that tag' \ 'test $(git tag -l mytag) = mytag' From a7659747c2eeb0c601ced321446da92149310cd0 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Sun, 4 Dec 2016 20:45:59 +0000 Subject: [PATCH 0024/1540] GIT-VERSION-GEN: do not force abbreviation length used by 'describe' The default version name for a Git binary is computed by running "git describe" on the commit the binary is made out of, basing on a tag whose name matches "v[0-9]*", e.g. v2.11.0-rc2-2-g7f1dc9. In the very early days, with 9b88fcef7d ("Makefile: use git-describe to mark the git version.", 2005-12-27), we used "--abbrev=4" to get absolute minimum number of abbreviated commit object name. This was later changed to match the default minimum of 7 with bf505158d0 ("Git 1.7.10.1", 2012-05-01). These days, the "default minimum" scales automatically depending on the size of the repository, and there is no point in specifying a particular abbreviation length; all we wanted since Git 1.7.10.1 days was to get "something reasonable we would use by default". Just drop "--abbrev=" from the invocation of "git describe" and let the command pick what it thinks is appropriate, taking the end user's configuration and the repository contents into account. Signed-off-by: Ramsay Jones Helped-by: Jeff King Signed-off-by: Junio C Hamano --- GIT-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 556fbfc104b1ed..f95b04bb365fb8 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -12,7 +12,7 @@ if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d ${GIT_DIR:-.git} -o -f .git && - VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) && + VN=$(git describe --match "v[0-9]*" HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) From 1f7c9261320576fcaaa5b4e50ad73336b17183e8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 30 Nov 2016 23:52:43 -0500 Subject: [PATCH 0025/1540] xdiff: drop XDL_FAST_HASH The xdiff code hashes every line of both sides of a diff, and then compares those hashes to find duplicates. The overall performance depends both on how fast we can compute the hashes, but also on how many hash collisions we see. The idea of XDL_FAST_HASH is to speed up the hash computation. But the generated hashes have worse collision behavior. This means that in some cases it speeds diffs up (running "git log -p" on git.git improves by ~8% with it), but in others it can slow things down. One pathological case saw over a 100x slowdown[1]. There may be a better hash function that covers both properties, but in the meantime we are better off with the original hash. It's slightly slower in the common case, but it has fewer surprising pathological cases. [1] http://public-inbox.org/git/20141222041944.GA441@peff.net/ Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 9 ---- config.mak.uname | 3 -- xdiff/xutils.c | 106 ----------------------------------------------- 3 files changed, 118 deletions(-) diff --git a/Makefile b/Makefile index f53fcc90d71b7e..f61076997aa499 100644 --- a/Makefile +++ b/Makefile @@ -338,11 +338,6 @@ all:: # # Define NATIVE_CRLF if your platform uses CRLF for line endings. # -# Define XDL_FAST_HASH to use an alternative line-hashing method in -# the diff algorithm. It gives a nice speedup if your processor has -# fast unaligned word loads. Does NOT work on big-endian systems! -# Enabled by default on x86_64. -# # Define GIT_USER_AGENT if you want to change how git identifies itself during # network interactions. The default is "git/$(GIT_VERSION)". # @@ -1485,10 +1480,6 @@ ifndef NO_MSGFMT_EXTENDED_OPTIONS MSGFMT += --check --statistics endif -ifneq (,$(XDL_FAST_HASH)) - BASIC_CFLAGS += -DXDL_FAST_HASH -endif - ifdef GMTIME_UNRELIABLE_ERRORS COMPAT_OBJS += compat/gmtime.o BASIC_CFLAGS += -DGMTIME_UNRELIABLE_ERRORS diff --git a/config.mak.uname b/config.mak.uname index b232908f8c8c2e..447f36ac2e31dd 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -17,9 +17,6 @@ endif # because maintaining the nesting to match is a pain. If # we had "elif" things would have been much nicer... -ifeq ($(uname_M),x86_64) - XDL_FAST_HASH = YesPlease -endif ifeq ($(uname_S),OSF1) # Need this for u_short definitions et al BASIC_CFLAGS += -D_OSF_SOURCE diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 027192a1c7f122..04d7b32e4e4a75 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -264,110 +264,6 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, return ha; } -#ifdef XDL_FAST_HASH - -#define REPEAT_BYTE(x) ((~0ul / 0xff) * (x)) - -#define ONEBYTES REPEAT_BYTE(0x01) -#define NEWLINEBYTES REPEAT_BYTE(0x0a) -#define HIGHBITS REPEAT_BYTE(0x80) - -/* Return the high bit set in the first byte that is a zero */ -static inline unsigned long has_zero(unsigned long a) -{ - return ((a - ONEBYTES) & ~a) & HIGHBITS; -} - -static inline long count_masked_bytes(unsigned long mask) -{ - if (sizeof(long) == 8) { - /* - * Jan Achrenius on G+: microoptimized version of - * the simpler "(mask & ONEBYTES) * ONEBYTES >> 56" - * that works for the bytemasks without having to - * mask them first. - */ - /* - * return mask * 0x0001020304050608 >> 56; - * - * Doing it like this avoids warnings on 32-bit machines. - */ - long a = (REPEAT_BYTE(0x01) / 0xff + 1); - return mask * a >> (sizeof(long) * 7); - } else { - /* Carl Chatfield / Jan Achrenius G+ version for 32-bit */ - /* (000000 0000ff 00ffff ffffff) -> ( 1 1 2 3 ) */ - long a = (0x0ff0001 + mask) >> 23; - /* Fix the 1 for 00 case */ - return a & mask; - } -} - -unsigned long xdl_hash_record(char const **data, char const *top, long flags) -{ - unsigned long hash = 5381; - unsigned long a = 0, mask = 0; - char const *ptr = *data; - char const *end = top - sizeof(unsigned long) + 1; - - if (flags & XDF_WHITESPACE_FLAGS) - return xdl_hash_record_with_whitespace(data, top, flags); - - ptr -= sizeof(unsigned long); - do { - hash += hash << 5; - hash ^= a; - ptr += sizeof(unsigned long); - if (ptr >= end) - break; - a = *(unsigned long *)ptr; - /* Do we have any '\n' bytes in this word? */ - mask = has_zero(a ^ NEWLINEBYTES); - } while (!mask); - - if (ptr >= end) { - /* - * There is only a partial word left at the end of the - * buffer. Because we may work with a memory mapping, - * we have to grab the rest byte by byte instead of - * blindly reading it. - * - * To avoid problems with masking in a signed value, - * we use an unsigned char here. - */ - const char *p; - for (p = top - 1; p >= ptr; p--) - a = (a << 8) + *((const unsigned char *)p); - mask = has_zero(a ^ NEWLINEBYTES); - if (!mask) - /* - * No '\n' found in the partial word. Make a - * mask that matches what we read. - */ - mask = 1UL << (8 * (top - ptr) + 7); - } - - /* The mask *below* the first high bit set */ - mask = (mask - 1) & ~mask; - mask >>= 7; - hash += hash << 5; - hash ^= a & mask; - - /* Advance past the last (possibly partial) word */ - ptr += count_masked_bytes(mask); - - if (ptr < top) { - assert(*ptr == '\n'); - ptr++; - } - - *data = ptr; - - return hash; -} - -#else /* XDL_FAST_HASH */ - unsigned long xdl_hash_record(char const **data, char const *top, long flags) { unsigned long ha = 5381; char const *ptr = *data; @@ -384,8 +280,6 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { return ha; } -#endif /* XDL_FAST_HASH */ - unsigned int xdl_hashbits(unsigned int size) { unsigned int val = 1, bits = 0; From 89d38fb26664038a85eb5a0da8fa4d23228e450d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 7 Dec 2016 11:11:26 -0800 Subject: [PATCH 0026/1540] wt-status: implement opportunisitc index update correctly The require_clean_work_tree() function calls hold_locked_index() with die_on_error=0 to signal that it is OK if it fails to obtain the lock, but unconditionally calls update_index_if_able(), which will try to write into fd=-1. Signed-off-by: Junio C Hamano --- wt-status.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wt-status.c b/wt-status.c index a2e9d332d8332b..a715e71906a9a2 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2258,11 +2258,12 @@ int has_uncommitted_changes(int ignore_submodules) int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int err = 0; + int err = 0, fd; - hold_locked_index(lock_file, 0); + fd = hold_locked_index(lock_file, 0); refresh_cache(REFRESH_QUIET); - update_index_if_able(&the_index, lock_file); + if (0 <= fd) + update_index_if_able(&the_index, lock_file); rollback_lock_file(lock_file); if (has_unstaged_changes(ignore_submodules)) { From b3e83cc752e905e063d0930c682a06de5034074f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 7 Dec 2016 10:33:54 -0800 Subject: [PATCH 0027/1540] hold_locked_index(): align error handling with hold_lockfile_for_update() Callers of the hold_locked_index() function pass 0 when they want to prepare to write a new version of the index file without wishing to die or emit an error message when the request fails (e.g. somebody else already held the lock), and pass 1 when they want the call to die upon failure. This option is called LOCK_DIE_ON_ERROR by the underlying lockfile API, and the hold_locked_index() function translates the paramter to LOCK_DIE_ON_ERROR when calling the hold_lock_file_for_update(). Replace these hardcoded '1' with LOCK_DIE_ON_ERROR and stop translating. Callers other than the ones that are replaced with this change pass '0' to the function; no behaviour change is intended with this patch. Signed-off-by: Junio C Hamano --- Among the callers of hold_locked_index() that passes 0: - diff.c::refresh_index_quietly() at the end of "git diff" is an opportunistic update; it leaks the lockfile structure but it is just before the program exits and nobody should care. - builtin/describe.c::cmd_describe(), builtin/commit.c::cmd_status(), sequencer.c::read_and_refresh_cache() are all opportunistic updates and they are OK. - builtin/update-index.c::cmd_update_index() takes a lock upfront but we may end up not needing to update the index (i.e. the entries may be fully up-to-date), in which case we do not need to issue an error upon failure to acquire the lock. We do diagnose and die if we indeed need to update, so it is OK. - wt-status.c::require_clean_work_tree() IS BUGGY. It asks silence, does not check the returned value. Compare with callsites like cmd_describe() and cmd_status() to notice that it is wrong to call update_index_if_able() unconditionally. --- apply.c | 2 +- builtin/add.c | 2 +- builtin/am.c | 6 +++--- builtin/checkout-index.c | 2 +- builtin/checkout.c | 4 ++-- builtin/clone.c | 2 +- builtin/commit.c | 8 ++++---- builtin/merge.c | 6 +++--- builtin/mv.c | 2 +- builtin/read-tree.c | 2 +- builtin/reset.c | 2 +- builtin/rm.c | 2 +- builtin/update-index.c | 1 + merge-recursive.c | 2 +- read-cache.c | 7 ++----- rerere.c | 2 +- sequencer.c | 2 +- t/helper/test-scrap-cache-tree.c | 2 +- 18 files changed, 27 insertions(+), 29 deletions(-) diff --git a/apply.c b/apply.c index 705cf562f07098..2ed808d429969f 100644 --- a/apply.c +++ b/apply.c @@ -4688,7 +4688,7 @@ static int apply_patch(struct apply_state *state, state->index_file, LOCK_DIE_ON_ERROR); else - state->newfd = hold_locked_index(state->lock_file, 1); + state->newfd = hold_locked_index(state->lock_file, LOCK_DIE_ON_ERROR); } if (state->check_index && read_apply_cache(state) < 0) { diff --git a/builtin/add.c b/builtin/add.c index e8fb80b36e7386..9f53f020d0fc71 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -361,7 +361,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) add_new_files = !take_worktree_changes && !refresh_only; require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | diff --git a/builtin/am.c b/builtin/am.c index 6981f42ce986dc..bb5da422fc1ae0 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1119,7 +1119,7 @@ static void refresh_and_write_cache(void) { struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write index file")); @@ -1976,7 +1976,7 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) return -1; lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); @@ -2016,7 +2016,7 @@ static int merge_tree(struct tree *tree) return -1; lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 30a49d9f424c0a..07631d0c9c59f6 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -205,7 +205,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (index_opt && !state.base_dir_len && !to_tempfile) { state.refresh_cache = 1; state.istate = &the_index; - newfd = hold_locked_index(&lock_file, 1); + newfd = hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); } /* Check out named files first */ diff --git a/builtin/checkout.c b/builtin/checkout.c index 512492aad9099d..bfe685c198cd38 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -274,7 +274,7 @@ static int checkout_paths(const struct checkout_opts *opts, lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(&opts->pathspec) < 0) return error(_("index file corrupt")); @@ -467,7 +467,7 @@ static int merge_working_tree(const struct checkout_opts *opts, int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(NULL) < 0) return error(_("index file corrupt")); diff --git a/builtin/clone.c b/builtin/clone.c index 6c76a6ed66fef5..892bdbfe3f2752 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -711,7 +711,7 @@ static int checkout(int submodule_progress) setup_work_tree(); lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof opts); opts.update = 1; diff --git a/builtin/commit.c b/builtin/commit.c index 8976c3d29bf817..b910e760179a12 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -351,7 +351,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (interactive) { char *old_index_env = NULL; - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); @@ -396,7 +396,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix * (B) on failure, rollback the real index. */ if (all || (also && pathspec.nr)) { - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); @@ -416,7 +416,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix * We still need to refresh the index here. */ if (!only && !pathspec.nr) { - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); if (active_cache_changed || !cache_tree_fully_valid(active_cache_tree)) @@ -468,7 +468,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (read_cache() < 0) die(_("cannot read the index")); - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); update_main_cache_tree(WRITE_TREE_SILENT); diff --git a/builtin/merge.c b/builtin/merge.c index b65eeaa87d303b..0070bf255612ea 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -634,7 +634,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, { static struct lock_file lock; - hold_locked_index(&lock, 1); + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); if (active_cache_changed && write_locked_index(&the_index, &lock, COMMIT_LOCK)) @@ -671,7 +671,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); - hold_locked_index(&lock, 1); + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (clean < 0) @@ -781,7 +781,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) struct commit_list *parents, **pptr = &parents; static struct lock_file lock; - hold_locked_index(&lock, 1); + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); if (active_cache_changed && write_locked_index(&the_index, &lock, COMMIT_LOCK)) diff --git a/builtin/mv.c b/builtin/mv.c index 2f43877bc9a17c..43adf92ba64426 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -126,7 +126,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (--argc < 1) usage_with_options(builtin_mv_usage, builtin_mv_options); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die(_("index file corrupt")); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 9bd1fd755ef038..fa6edb35b21ced 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -150,7 +150,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) diff --git a/builtin/reset.c b/builtin/reset.c index c04ac076dc53b9..8ab915bfcb71ae 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -354,7 +354,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type != SOFT) { struct lock_file *lock = xcalloc(1, sizeof(*lock)); - hold_locked_index(lock, 1); + hold_locked_index(lock, LOCK_DIE_ON_ERROR); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) diff --git a/builtin/rm.c b/builtin/rm.c index 3f3e24eb36af03..7f15a3d7f82a7b 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -292,7 +292,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) setup_work_tree(); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die(_("index file corrupt")); diff --git a/builtin/update-index.c b/builtin/update-index.c index f3f07e7f1cb2d9..d530e89368b42b 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -1012,6 +1012,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ lock_file = xcalloc(1, sizeof(struct lock_file)); + /* we will diagnose later if it turns out that we need to update it */ newfd = hold_locked_index(lock_file, 0); if (newfd < 0) lock_error = errno; diff --git a/merge-recursive.c b/merge-recursive.c index 9041c2f149c011..8442068716035b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -2124,7 +2124,7 @@ int merge_recursive_generic(struct merge_options *o, } } - hold_locked_index(lock, 1); + hold_locked_index(lock, LOCK_DIE_ON_ERROR); clean = merge_recursive(o, head_commit, next_commit, ca, result); if (clean < 0) diff --git a/read-cache.c b/read-cache.c index db5d910642663e..f92a912dcb224b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1425,12 +1425,9 @@ static int read_index_extension(struct index_state *istate, return 0; } -int hold_locked_index(struct lock_file *lk, int die_on_error) +int hold_locked_index(struct lock_file *lk, int lock_flags) { - return hold_lock_file_for_update(lk, get_index_file(), - die_on_error - ? LOCK_DIE_ON_ERROR - : 0); + return hold_lock_file_for_update(lk, get_index_file(), lock_flags); } int read_index(struct index_state *istate) diff --git a/rerere.c b/rerere.c index 5d083ca572df0c..3bd55caf3b0961 100644 --- a/rerere.c +++ b/rerere.c @@ -708,7 +708,7 @@ static void update_paths(struct string_list *update) { int i; - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); for (i = 0; i < update->nr; i++) { struct string_list_item *item = &update->items[i]; diff --git a/sequencer.c b/sequencer.c index 30b10ba143959b..7fc1e2a5df0ada 100644 --- a/sequencer.c +++ b/sequencer.c @@ -370,7 +370,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, char **xopt; static struct lock_file index_lock; - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); read_cache(); diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 27fe0405b88767..d2a63bea4346fb 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -8,7 +8,7 @@ static struct lock_file index_lock; int cmd_main(int ac, const char **av) { setup_git_directory(); - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die("unable to read index file"); active_cache_tree = NULL; From 3f061bf514667497a948804828064b9b9c3b249b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 7 Dec 2016 10:56:26 -0800 Subject: [PATCH 0028/1540] lockfile: LOCK_REPORT_ON_ERROR The "libify sequencer" topic stopped passing the die_on_error option to hold_locked_index(), and this lost an error message from "git merge --ff-only $commit" when there are competing updates in progress. The command still exits with a non-zero status, but that is not of much help for an interactive user. The last thing the command says is "Updating $from..$to". We used to follow it with a big error message that makes it clear that "merge --ff-only" did not succeed. What is sad is that we should have noticed this regression while reviewing the change. It was clear that the update to the checkout_fast_forward() function made a failing hold_locked_index() silent, but the only caller of the checkout_fast_forward() function had this comment: if (checkout_fast_forward(from, to, 1)) - exit(128); /* the callee should have complained already */ + return -1; /* the callee should have complained already */ which clearly contradicted the assumption X-<. Add a new option LOCK_REPORT_ON_ERROR that can be passed instead of LOCK_DIE_ON_ERROR to the hold_lock*() family of functions and teach checkout_fast_forward() to use it to fix this regression. After going thourgh all calls to hold_lock*() family of functions that used to pass LOCK_DIE_ON_ERROR but were modified to pass 0 in the "libify sequencer" topic "git show --first-parent 2a4062a4a8", it appears that this is the only one that has become silent. Many others used to give detailed report that talked about "there may be competing Git process running" but with the series merged they now only give a single liner "Unable to lock ...", some of which may have to be tweaked further, but at least they say something, unlike the one this patch fixes. Reported-by: Robbie Iannucci Signed-off-by: Junio C Hamano --- lockfile.c | 12 ++++++++++-- lockfile.h | 8 +++++++- merge.c | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lockfile.c b/lockfile.c index 9268cdf325f388..aa69210d8b3a90 100644 --- a/lockfile.c +++ b/lockfile.c @@ -174,8 +174,16 @@ int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path, int flags, long timeout_ms) { int fd = lock_file_timeout(lk, path, flags, timeout_ms); - if (fd < 0 && (flags & LOCK_DIE_ON_ERROR)) - unable_to_lock_die(path, errno); + if (fd < 0) { + if (flags & LOCK_DIE_ON_ERROR) + unable_to_lock_die(path, errno); + if (flags & LOCK_REPORT_ON_ERROR) { + struct strbuf buf = STRBUF_INIT; + unable_to_lock_message(path, errno, &buf); + error("%s", buf.buf); + strbuf_release(&buf); + } + } return fd; } diff --git a/lockfile.h b/lockfile.h index d26ad27b2b2df2..16775a7d79bba0 100644 --- a/lockfile.h +++ b/lockfile.h @@ -129,10 +129,16 @@ struct lock_file { /* * If a lock is already taken for the file, `die()` with an error * message. If this flag is not specified, trying to lock a file that - * is already locked returns -1 to the caller. + * is already locked silently returns -1 to the caller, or ... */ #define LOCK_DIE_ON_ERROR 1 +/* + * ... this flag can be passed instead to return -1 and give the usual + * error message upon an error. + */ +#define LOCK_REPORT_ON_ERROR 2 + /* * Usually symbolic links in the destination path are resolved. This * means that (1) the lockfile is created by adding ".lock" to the diff --git a/merge.c b/merge.c index 23866c91655638..04ee5fc911c8d8 100644 --- a/merge.c +++ b/merge.c @@ -57,7 +57,7 @@ int checkout_fast_forward(const unsigned char *head, refresh_cache(REFRESH_QUIET); - if (hold_locked_index(lock_file, 0) < 0) + if (hold_locked_index(lock_file, LOCK_REPORT_ON_ERROR) < 0) return -1; memset(&trees, 0, sizeof(trees)); From eba286e31012335370aedb7572ff51df549eb3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:23:55 +0100 Subject: [PATCH 0029/1540] t7004-tag: delete unnecessary tags with test_when_finished MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The '--force is moot with a non-existing tag name' test creates two new tags, which are then deleted right after the test is finished, outside the test_expect_success block, allowing 'git tag -d's output to pollute the test output. Use test_when_finished to delete those tags. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- t/t7004-tag.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 8b0f71a2ac15d6..396cffeeb5adee 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -122,11 +122,11 @@ test_expect_success '--force can create a tag with the name of one existing' ' tag_exists mytag' test_expect_success '--force is moot with a non-existing tag name' ' + test_when_finished git tag -d newtag forcetag && git tag newtag >expect && git tag --force forcetag >actual && test_cmp expect actual ' -git tag -d newtag forcetag # deleting tags: From 9ffda48f53e086895975f2a435afff01c836dad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:23:56 +0100 Subject: [PATCH 0030/1540] t7004-tag: use test_config helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... instead of setting and then manually unsetting configuration variables, on one occasion even outside the test_expect_success block. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- t/t7004-tag.sh | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 396cffeeb5adee..920a1b4b2e58e4 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -297,11 +297,9 @@ EOF ' test_expect_success 'listing tags in column with column.*' ' - git config column.tag row && - git config column.ui dense && + test_config column.tag row && + test_config column.ui dense && COLUMNS=40 git tag -l >actual && - git config --unset column.ui && - git config --unset column.tag && cat >expected <<\EOF && a1 aa1 cba t210 t211 v0.2.1 v1.0 v1.0.1 v1.1.3 @@ -314,9 +312,8 @@ test_expect_success 'listing tag with -n --column should fail' ' ' test_expect_success 'listing tags -n in column with column.ui ignored' ' - git config column.ui "row dense" && + test_config column.ui "row dense" && COLUMNS=40 git tag -l -n >actual && - git config --unset column.ui && cat >expected <<\EOF && a1 Foo aa1 Foo @@ -1200,11 +1197,10 @@ test_expect_success GPG,RFC1991 \ ' # try to sign with bad user.signingkey -git config user.signingkey BobTheMouse test_expect_success GPG \ 'git tag -s fails if gpg is misconfigured (bad key)' \ - 'test_must_fail git tag -s -m tail tag-gpg-failure' -git config --unset user.signingkey + 'test_config user.signingkey BobTheMouse && + test_must_fail git tag -s -m tail tag-gpg-failure' # try to produce invalid signature test_expect_success GPG \ @@ -1484,7 +1480,7 @@ test_expect_success 'reverse lexical sort' ' ' test_expect_success 'configured lexical sort' ' - git config tag.sort "v:refname" && + test_config tag.sort "v:refname" && git tag -l "foo*" >actual && cat >expect <<-\EOF && foo1.3 @@ -1495,6 +1491,7 @@ test_expect_success 'configured lexical sort' ' ' test_expect_success 'option override configured sort' ' + test_config tag.sort "v:refname" && git tag -l --sort=-refname "foo*" >actual && cat >expect <<-\EOF && foo1.6 @@ -1509,13 +1506,12 @@ test_expect_success 'invalid sort parameter on command line' ' ' test_expect_success 'invalid sort parameter in configuratoin' ' - git config tag.sort "v:notvalid" && + test_config tag.sort "v:notvalid" && test_must_fail git tag -l "foo*" ' test_expect_success 'version sort with prerelease reordering' ' - git config --unset tag.sort && - git config versionsort.prereleaseSuffix -rc && + test_config versionsort.prereleaseSuffix -rc && git tag foo1.6-rc1 && git tag foo1.6-rc2 && git tag -l --sort=version:refname "foo*" >actual && @@ -1530,6 +1526,7 @@ test_expect_success 'version sort with prerelease reordering' ' ' test_expect_success 'reverse version sort with prerelease reordering' ' + test_config versionsort.prereleaseSuffix -rc && git tag -l --sort=-version:refname "foo*" >actual && cat >expect <<-\EOF && foo1.10 From 0c1b4878dec423a0d322bffc5607de584aab214e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:23:57 +0100 Subject: [PATCH 0031/1540] t7004-tag: add version sort tests to show prerelease reordering issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version sort with prerelease reordering sometimes puts tagnames in the wrong order, when the common part of two compared tagnames ends with the leading character(s) of one or more configured prerelease suffixes. Add tests that demonstrate these issues. The unrelated '--format should list tags as per format given' test later uses tags matching the same prefix as the version sort tests, thus was affected by the new tags added for the new tests in this patch. Change that test to perform its checks on a different set of tags. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- t/t7004-tag.sh | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 920a1b4b2e58e4..6445aae29ba77c 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1538,6 +1538,32 @@ test_expect_success 'reverse version sort with prerelease reordering' ' test_cmp expect actual ' +test_expect_failure 'version sort with prerelease reordering and common leading character' ' + test_config versionsort.prereleaseSuffix -before && + git tag foo1.7-before1 && + git tag foo1.7 && + git tag foo1.7-after1 && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7 + foo1.7-after1 + EOF + test_cmp expect actual +' + +test_expect_failure 'version sort with prerelease reordering, multiple suffixes and common leading character' ' + test_config versionsort.prereleaseSuffix -before && + git config --add versionsort.prereleaseSuffix -after && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7-after1 + foo1.7 + EOF + test_cmp expect actual +' + run_with_limited_stack () { (ulimit -s 128 && "$@") } @@ -1566,13 +1592,11 @@ EOF" 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 + refname : refs/tags/v1.0 + refname : refs/tags/v1.0.1 + refname : refs/tags/v1.1.3 EOF - git tag -l --format="refname : %(refname)" "foo*" >actual && + git tag -l --format="refname : %(refname)" "v1*" >actual && test_cmp expect actual ' From 109064a0313c8a7c337f37936077ef3c9171d7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:23:58 +0100 Subject: [PATCH 0032/1540] versioncmp: pass full tagnames to swap_prereleases() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The swap_prereleases() helper function is responsible for finding configured prerelease suffixes in a pair of tagnames to be compared, but this function currently gets to see only the parts of those two tagnames starting at the first different character. To fix some issues related to the common part of two tagnames overlapping with leading part of a prerelease suffix, this helper function must see both full tagnames. In preparation for the fix in the following patch, refactor swap_prereleases() and its caller to pass two full tagnames and an additional offset indicating the position of the first different character. While updating the comment describing that function, remove the sentence about not dealing with both tagnames having the same suffix. Currently it doesn't add much value (we know that there is a different character, so it's obvious that it can't possibly be the same suffix in both), and at the end of this patch series it won't even be true anymore. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- versioncmp.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/versioncmp.c b/versioncmp.c index 80bfd109fa1285..a55c23ad57041a 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -25,32 +25,28 @@ static const struct string_list *prereleases; static int initialized; /* - * p1 and p2 point to the first different character in two strings. If - * either p1 or p2 starts with a prerelease suffix, it will be forced - * to be on top. + * off is the offset of the first different character in the two strings + * s1 and s2. If either s1 or s2 contains a prerelease suffix starting + * at that offset, it will be forced to be on top. * - * If both p1 and p2 start with (different) suffix, the order is - * determined by config file. - * - * Note that we don't have to deal with the situation when both p1 and - * p2 start with the same suffix because the common part is already - * consumed by the caller. + * If both s1 and s2 contain a (different) suffix at that position, + * their order is determined by the order of those two suffixes in the + * configuration. * * Return non-zero if *diff contains the return value for versioncmp() */ -static int swap_prereleases(const void *p1_, - const void *p2_, +static int swap_prereleases(const char *s1, + const char *s2, + int off, int *diff) { - const char *p1 = p1_; - const char *p2 = p2_; int i, i1 = -1, i2 = -1; for (i = 0; i < prereleases->nr; i++) { const char *suffix = prereleases->items[i].string; - if (i1 == -1 && starts_with(p1, suffix)) + if (i1 == -1 && starts_with(s1 + off, suffix)) i1 = i; - if (i2 == -1 && starts_with(p2, suffix)) + if (i2 == -1 && starts_with(s2 + off, suffix)) i2 = i; } if (i1 == -1 && i2 == -1) @@ -121,7 +117,8 @@ int versioncmp(const char *s1, const char *s2) initialized = 1; prereleases = git_config_get_value_multi("versionsort.prereleasesuffix"); } - if (prereleases && swap_prereleases(p1 - 1, p2 - 1, &diff)) + if (prereleases && swap_prereleases(s1, s2, (const char *) p1 - s1 - 1, + &diff)) return diff; state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))]; From b8231660fa95f6e9e07b9e2483e254c2de045275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:23:59 +0100 Subject: [PATCH 0033/1540] versioncmp: cope with common part overlapping with prerelease suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version sort with prerelease reordering sometimes puts tagnames in the wrong order, when the common part of two compared tagnames overlaps with the leading character(s) of one or more configured prerelease suffixes. Note the position of "v2.1.0-beta-1": $ git -c versionsort.prereleaseSuffix=-beta \ tag -l --sort=version:refname v2.1.* v2.1.0-beta-2 v2.1.0-beta-3 v2.1.0 v2.1.0-RC1 v2.1.0-RC2 v2.1.0-beta-1 v2.1.1 v2.1.2 The reason is that when comparing a pair of tagnames, first versioncmp() looks for the first different character in a pair of tagnames, and then the swap_prereleases() helper function looks for a configured prerelease suffix _starting at_ that character. Thus, when in the above example the sorting algorithm happens to compare the tagnames "v2.1.0-beta-1" and "v2.1.0-RC2", swap_prereleases() tries to match the suffix "-beta" against "beta-1" to no avail, and the two tagnames erroneously end up being ordered lexicographically. To fix this issue change swap_prereleases() to look for configured prerelease suffixes _containing_ the position of that first different character. Care must be taken, when a configured suffix is longer than the tagnames' common part up to the first different character, to avoid reading memory before the beginning of the tagnames. Add a test that uses an exceptionally long prerelease suffix to check for this, in the hope that in case of a regression the illegal memory access causes a segfault in 'git tag' on one of the commonly used platforms (the test happens to pass successfully on my Linux system with the safety check removed), or at least makes valgrind complain. Under some circumstances it's possible that more than one prerelease suffixes can be found in the same tagname around that first different character. With this simple bugfix patch such a tagname is sorted according to the contained suffix that comes first in the configuration for now. This is less than ideal in some cases, and the following patch will take care of those. Reported-by: Leho Kraav Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 ++++++-- t/t7004-tag.sh | 9 +++++++-- versioncmp.c | 28 +++++++++++++++++++++------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index a0ab66aae70db9..2e053f9048876c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -3047,8 +3047,12 @@ versionsort.prereleaseSuffix:: This variable can be specified multiple times, once per suffix. The order of suffixes in the config file determines the sorting order (e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX -is sorted before 1.0-rcXX). The sorting order between different -suffixes is undefined if they are in multiple config files. +is sorted before 1.0-rcXX). +If more than one suffixes match the same tagname, then that tagname will +be sorted according to the matching suffix which comes first in the +configuration. +The sorting order between different suffixes is undefined if they are +in multiple config files. web.browser:: Specify a web browser that may be used by some commands. diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 6445aae29ba77c..c7aaace8cde547 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1538,7 +1538,7 @@ test_expect_success 'reverse version sort with prerelease reordering' ' test_cmp expect actual ' -test_expect_failure 'version sort with prerelease reordering and common leading character' ' +test_expect_success 'version sort with prerelease reordering and common leading character' ' test_config versionsort.prereleaseSuffix -before && git tag foo1.7-before1 && git tag foo1.7 && @@ -1552,7 +1552,7 @@ test_expect_failure 'version sort with prerelease reordering and common leading test_cmp expect actual ' -test_expect_failure 'version sort with prerelease reordering, multiple suffixes and common leading character' ' +test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' ' test_config versionsort.prereleaseSuffix -before && git config --add versionsort.prereleaseSuffix -after && git tag -l --sort=version:refname "foo1.7*" >actual && @@ -1564,6 +1564,11 @@ test_expect_failure 'version sort with prerelease reordering, multiple suffixes test_cmp expect actual ' +test_expect_success 'version sort with very long prerelease suffix' ' + test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix && + git tag -l --sort=version:refname +' + run_with_limited_stack () { (ulimit -s 128 && "$@") } diff --git a/versioncmp.c b/versioncmp.c index a55c23ad57041a..f86ac562e294c5 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -26,12 +26,15 @@ static int initialized; /* * off is the offset of the first different character in the two strings - * s1 and s2. If either s1 or s2 contains a prerelease suffix starting - * at that offset, it will be forced to be on top. + * s1 and s2. If either s1 or s2 contains a prerelease suffix containing + * that offset, then that string will be forced to be on top. * - * If both s1 and s2 contain a (different) suffix at that position, + * If both s1 and s2 contain a (different) suffix around that position, * their order is determined by the order of those two suffixes in the * configuration. + * If any of the strings contains more than one different suffixes around + * that position, then that string is sorted according to the contained + * suffix which comes first in the configuration. * * Return non-zero if *diff contains the return value for versioncmp() */ @@ -44,10 +47,21 @@ static int swap_prereleases(const char *s1, for (i = 0; i < prereleases->nr; i++) { const char *suffix = prereleases->items[i].string; - if (i1 == -1 && starts_with(s1 + off, suffix)) - i1 = i; - if (i2 == -1 && starts_with(s2 + off, suffix)) - i2 = i; + int j, start, suffix_len = strlen(suffix); + if (suffix_len < off) + start = off - suffix_len + 1; + else + start = 0; + for (j = start; j <= off; j++) + if (i1 == -1 && starts_with(s1 + j, suffix)) { + i1 = i; + break; + } + for (j = start; j <= off; j++) + if (i2 == -1 && starts_with(s2 + j, suffix)) { + i2 = i; + break; + } } if (i1 == -1 && i2 == -1) return 0; From 51acfa9db588ddd8c3aff2acbc1a85ae7ac239f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:24:00 +0100 Subject: [PATCH 0034/1540] versioncmp: use earliest-longest contained suffix to determine sorting order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When comparing tagnames, it is possible that a tagname contains more than one of the configured prerelease suffixes around the first different character. After fixing a bug in the previous commit such a tagname is sorted according to the contained suffix which comes first in the configuration. This is, however, not quite the right thing to do in the following corner cases: 1. $ git -c versionsort.suffix=-bar -c versionsort.suffix=-foo-baz -c versionsort.suffix=-foo-bar tag -l --sort=version:refname 'v1*' v1.0-foo-bar v1.0-foo-baz The suffix of the tagname 'v1.0-foo-bar' is clearly '-foo-bar', so it should be listed last. However, as it also contains '-bar' around the first different character, it is listed first instead, because that '-bar' suffix comes first the configuration. 2. One of the configured suffixes starts with the other: $ git -c versionsort.prereleasesuffix=-pre \ -c versionsort.prereleasesuffix=-prerelease \ tag -l --sort=version:refname 'v2*' v2.0-prerelease1 v2.0-pre1 v2.0-pre2 Here the tagname 'v2.0-prerelease1' should be the last. When comparing 'v2.0-pre1' and 'v2.0-prerelease1' the first different characters are '1' and 'r', respectively. Since this first different character must be part of the configured suffix, the '-pre' suffix is not recognized in the first tagname. OTOH, the '-prerelease' suffix is properly recognized in 'v2.0-prerelease1', thus it is listed first. Improve version sort in these corner cases, and - look for a configured prerelease suffix containing the first different character or ending right before it, so the '-pre' suffixes are recognized in case (2). This also means that when comparing tagnames 'v2.0-pre1' and 'v2.0-pre2', swap_prereleases() would find the '-pre' suffix in both, but then it will return "undecided" and the caller will do the right thing by sorting based in '1' and '2'. - If the tagname contains more than one suffix, then give precedence to the contained suffix that starts at the earliest offset in the tagname to address (1). - If there are more than one suffixes starting at that earliest position, then give precedence to the longest of those suffixes, thus ensuring that in (2) the tagname 'v2.0-prerelease1' won't be sorted based on the '-pre' suffix. Add tests for these corner cases and adjust the documentation accordingly. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- Documentation/config.txt | 6 ++++-- t/t7004-tag.sh | 31 +++++++++++++++++++++++++++++++ versioncmp.c | 33 +++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 2e053f9048876c..9078c8c4f4db14 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -3049,8 +3049,10 @@ order of suffixes in the config file determines the sorting order (e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX is sorted before 1.0-rcXX). If more than one suffixes match the same tagname, then that tagname will -be sorted according to the matching suffix which comes first in the -configuration. +be sorted according to the suffix which starts at the earliest position in +the tagname. If more than one different matching suffixes start at +that earliest position, then that tagname will be sorted according to the +longest of those suffixes. The sorting order between different suffixes is undefined if they are in multiple config files. diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index c7aaace8cde547..e2efe312dcf0ac 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1564,6 +1564,37 @@ test_expect_success 'version sort with prerelease reordering, multiple suffixes test_cmp expect actual ' +test_expect_success 'version sort with prerelease reordering, multiple suffixes match the same tag' ' + test_config versionsort.prereleaseSuffix -bar && + git config --add versionsort.prereleaseSuffix -foo-baz && + git config --add versionsort.prereleaseSuffix -foo-bar && + git tag foo1.8-foo-bar && + git tag foo1.8-foo-baz && + git tag foo1.8 && + git tag -l --sort=version:refname "foo1.8*" >actual && + cat >expect <<-\EOF && + foo1.8-foo-baz + foo1.8-foo-bar + foo1.8 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with prerelease reordering, multiple suffixes match starting at the same position' ' + test_config versionsort.prereleaseSuffix -pre && + git config --add versionsort.prereleaseSuffix -prerelease && + git tag foo1.9-pre1 && + git tag foo1.9-pre2 && + git tag foo1.9-prerelease1 && + git tag -l --sort=version:refname "foo1.9*" >actual && + cat >expect <<-\EOF && + foo1.9-pre1 + foo1.9-pre2 + foo1.9-prerelease1 + EOF + test_cmp expect actual +' + test_expect_success 'version sort with very long prerelease suffix' ' test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix && git tag -l --sort=version:refname diff --git a/versioncmp.c b/versioncmp.c index f86ac562e294c5..ae0a9199bd3b6d 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -27,14 +27,18 @@ static int initialized; /* * off is the offset of the first different character in the two strings * s1 and s2. If either s1 or s2 contains a prerelease suffix containing - * that offset, then that string will be forced to be on top. + * that offset or a suffix ends right before that offset, then that + * string will be forced to be on top. * * If both s1 and s2 contain a (different) suffix around that position, * their order is determined by the order of those two suffixes in the * configuration. * If any of the strings contains more than one different suffixes around * that position, then that string is sorted according to the contained - * suffix which comes first in the configuration. + * suffix which starts at the earliest offset in that string. + * If more than one different contained suffixes start at that earliest + * offset, then that string is sorted according to the longest of those + * suffixes. * * Return non-zero if *diff contains the return value for versioncmp() */ @@ -44,27 +48,40 @@ static int swap_prereleases(const char *s1, int *diff) { int i, i1 = -1, i2 = -1; + int start_at1 = off, start_at2 = off, match_len1 = -1, match_len2 = -1; for (i = 0; i < prereleases->nr; i++) { const char *suffix = prereleases->items[i].string; - int j, start, suffix_len = strlen(suffix); + int j, start, end, suffix_len = strlen(suffix); if (suffix_len < off) - start = off - suffix_len + 1; + start = off - suffix_len; else start = 0; - for (j = start; j <= off; j++) - if (i1 == -1 && starts_with(s1 + j, suffix)) { + end = match_len1 < suffix_len ? start_at1 : start_at1-1; + for (j = start; j <= end; j++) + if (starts_with(s1 + j, suffix)) { i1 = i; + start_at1 = j; + match_len1 = suffix_len; break; } - for (j = start; j <= off; j++) - if (i2 == -1 && starts_with(s2 + j, suffix)) { + end = match_len2 < suffix_len ? start_at2 : start_at2-1; + for (j = start; j <= end; j++) + if (starts_with(s2 + j, suffix)) { i2 = i; + start_at2 = j; + match_len2 = suffix_len; break; } } if (i1 == -1 && i2 == -1) return 0; + if (i1 == i2) + /* Found the same suffix in both, e.g. "-rc" in "v1.0-rcX" + * and "v1.0-rcY": the caller should decide based on "X" + * and "Y". */ + return 0; + if (i1 >= 0 && i2 >= 0) *diff = i1 - i2; else if (i1 >= 0) From 90c0011619bd4cd4b14c366ab7a9affacb43276a Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Thu, 8 Dec 2016 13:03:24 -0800 Subject: [PATCH 0035/1540] submodule: use absolute path for computing relative path connecting The current caller of connect_work_tree_and_git_dir passes an absolute path for the `git_dir` parameter. In the future patch we will also pass in relative path for `git_dir`. Extend the functionality of connect_work_tree_and_git_dir to take relative paths for parameters. We could work around this in the future patch by computing the absolute path for the git_dir in the calling site, however accepting relative paths for either parameter makes the API for this function much harder to misuse. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- submodule.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/submodule.c b/submodule.c index 6f7d883de950af..d4f7afe2f1ad53 100644 --- a/submodule.c +++ b/submodule.c @@ -1223,27 +1223,28 @@ int merge_submodule(unsigned char result[20], const char *path, } /* Update gitfile and core.worktree setting to connect work tree and git dir */ -void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) +void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_) { struct strbuf file_name = STRBUF_INIT; struct strbuf rel_path = STRBUF_INIT; - const char *real_work_tree = xstrdup(real_path(work_tree)); + char *git_dir = xstrdup(real_path(git_dir_)); + char *work_tree = xstrdup(real_path(work_tree_)); /* Update gitfile */ strbuf_addf(&file_name, "%s/.git", work_tree); write_file(file_name.buf, "gitdir: %s", - relative_path(git_dir, real_work_tree, &rel_path)); + relative_path(git_dir, work_tree, &rel_path)); /* Update core.worktree setting */ strbuf_reset(&file_name); strbuf_addf(&file_name, "%s/config", git_dir); git_config_set_in_file(file_name.buf, "core.worktree", - relative_path(real_work_tree, git_dir, - &rel_path)); + relative_path(work_tree, git_dir, &rel_path)); strbuf_release(&file_name); strbuf_release(&rel_path); - free((void *)real_work_tree); + free(work_tree); + free(git_dir); } int parallel_submodules(void) From 89c8626557621ef000930da3c8a23fa03ad7e01a Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Thu, 8 Dec 2016 13:03:25 -0800 Subject: [PATCH 0036/1540] submodule helper: support super prefix Just like main commands in Git, the submodule helper needs access to the superproject prefix. Enable this in the git.c but have its own fuse in the helper code by having a flag to turn on the super prefix. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/submodule--helper.c | 31 ++++++++++++++++++++----------- git.c | 2 +- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 4beeda5f9f49d6..56438486672843 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1076,21 +1076,24 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, return 0; } +#define SUPPORT_SUPER_PREFIX (1<<0) + struct cmd_struct { const char *cmd; int (*fn)(int, const char **, const char *); + unsigned option; }; static struct cmd_struct commands[] = { - {"list", module_list}, - {"name", module_name}, - {"clone", module_clone}, - {"update-clone", update_clone}, - {"relative-path", resolve_relative_path}, - {"resolve-relative-url", resolve_relative_url}, - {"resolve-relative-url-test", resolve_relative_url_test}, - {"init", module_init}, - {"remote-branch", resolve_remote_submodule_branch} + {"list", module_list, 0}, + {"name", module_name, 0}, + {"clone", module_clone, 0}, + {"update-clone", update_clone, 0}, + {"relative-path", resolve_relative_path, 0}, + {"resolve-relative-url", resolve_relative_url, 0}, + {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"init", module_init, 0}, + {"remote-branch", resolve_remote_submodule_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) @@ -1100,9 +1103,15 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix) die(_("submodule--helper subcommand must be " "called with a subcommand")); - for (i = 0; i < ARRAY_SIZE(commands); i++) - if (!strcmp(argv[1], commands[i].cmd)) + for (i = 0; i < ARRAY_SIZE(commands); i++) { + if (!strcmp(argv[1], commands[i].cmd)) { + if (get_super_prefix() && + !(commands[i].option & SUPPORT_SUPER_PREFIX)) + die(_("%s doesn't support --super-prefix"), + commands[i].cmd); return commands[i].fn(argc - 1, argv + 1, prefix); + } + } die(_("'%s' is not a valid submodule--helper " "subcommand"), argv[1]); diff --git a/git.c b/git.c index efa1059fe060e4..98dcf6c5182a21 100644 --- a/git.c +++ b/git.c @@ -493,7 +493,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 }, + { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX}, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tag", cmd_tag, RUN_SETUP }, { "unpack-file", cmd_unpack_file, RUN_SETUP }, From 6f94351b0a4dd107623a35dc9081bf31c7ddac88 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Thu, 8 Dec 2016 13:03:26 -0800 Subject: [PATCH 0037/1540] test-lib-functions.sh: teach test_commit -C Specifically when setting up submodule tests, it comes in handy if we can create commits in repositories that are not at the root of the tested trash dir. Add "-C " similar to gits -C parameter that will perform the operation in the given directory. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- t/test-lib-functions.sh | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index fdaeb3a96bed36..579e812506ec7c 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -157,16 +157,21 @@ debug () { GIT_TEST_GDB=1 "$@" } -# Call test_commit with the arguments " [ [ []]]" +# Call test_commit with the arguments +# [-C ] [ [ []]]" # # This will commit a file with the given contents and the given commit # message, and tag the resulting commit with the given tag name. # # , , and all default to . +# +# If the first argument is "-C", the second argument is used as a path for +# the git invocations. test_commit () { notick= && signoff= && + indir= && while test $# != 0 do case "$1" in @@ -176,21 +181,26 @@ test_commit () { --signoff) signoff="$1" ;; + -C) + indir="$2" + shift + ;; *) break ;; esac shift done && + indir=${indir:+"$indir"/} && file=${2:-"$1.t"} && - echo "${3-$1}" > "$file" && - git add "$file" && + echo "${3-$1}" > "$indir$file" && + git ${indir:+ -C "$indir"} add "$file" && if test -z "$notick" then test_tick fi && - git commit $signoff -m "$1" && - git tag "${4:-$1}" + git ${indir:+ -C "$indir"} commit $signoff -m "$1" && + git ${indir:+ -C "$indir"} tag "${4:-$1}" } # Call test_merge with the arguments " ", where From 9512177b68263085fef84cdbd45ecdee7bfe2377 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, 12 Nov 2016 09:00:41 +0700 Subject: [PATCH 0038/1540] rebase: add --quit to cleanup rebase, leave everything else untouched MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are occasions when you decide to abort an in-progress rebase and move on to do something else but you forget to do "git rebase --abort" first. Or the rebase has been in progress for so long you forgot about it. By the time you realize that (e.g. by starting another rebase) it's already too late to retrace your steps. The solution is normally rm -r .git/ and continue with your life. But there could be two different directories for (and it obviously requires some knowledge of how rebase works), and the ".git" part could be much longer if you are not at top-dir, or in a linked worktree. And "rm -r" is very dangerous to do in .git, a mistake in there could destroy object database or other important data. Provide "git rebase --quit" for this use case, mimicking a precedent that is "git cherry-pick --quit". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 7 ++++++- contrib/completion/git-completion.bash | 4 ++-- git-rebase.sh | 6 +++++- t/t3407-rebase-abort.sh | 24 ++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6ed610a031eeec..f89245818f6c3f 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -12,7 +12,7 @@ SYNOPSIS [ []] 'git rebase' [-i | --interactive] [options] [--exec ] [--onto ] --root [] -'git rebase' --continue | --skip | --abort | --edit-todo +'git rebase' --continue | --skip | --abort | --quit | --edit-todo DESCRIPTION ----------- @@ -252,6 +252,11 @@ leave out at most one of A and B, in which case it defaults to HEAD. will be reset to where it was when the rebase operation was started. +--quit:: + Abort the rebase operation but HEAD is not reset back to the + original branch. The index and working tree are also left + unchanged as a result. + --keep-empty:: Keep the commits that do not change anything from its parents in the result. diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e3918c87e3adf3..2c134f8d9098d7 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1670,10 +1670,10 @@ _git_rebase () { local dir="$(__gitdir)" if [ -f "$dir"/rebase-merge/interactive ]; then - __gitcomp "--continue --skip --abort --edit-todo" + __gitcomp "--continue --skip --abort --quit --edit-todo" return elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then - __gitcomp "--continue --skip --abort" + __gitcomp "--continue --skip --abort --quit" return fi __git_complete_strategy && return diff --git a/git-rebase.sh b/git-rebase.sh index cf60c4390870ef..c62b17863e2132 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -43,6 +43,7 @@ continue! continue abort! abort and check out the original branch skip! skip current patch and continue edit-todo! edit the todo list during an interactive rebase +quit! abort but keep HEAD where it is " . git-sh-setup . git-sh-i18n @@ -239,7 +240,7 @@ do --verify) ok_to_skip_pre_rebase= ;; - --continue|--skip|--abort|--edit-todo) + --continue|--skip|--abort|--quit|--edit-todo) test $total_argc -eq 2 || usage action=${1##--} ;; @@ -402,6 +403,9 @@ abort) finish_rebase exit ;; +quit) + exec rm -rf "$state_dir" + ;; edit-todo) run_specific_rebase ;; diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index a6a6c40a98512b..910f2182843476 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -99,4 +99,28 @@ testrebase() { testrebase "" .git/rebase-apply testrebase " --merge" .git/rebase-merge +test_expect_success 'rebase --quit' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase master && + test_path_is_dir .git/rebase-apply && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test $(git rev-parse HEAD) = $head_before && + test ! -d .git/rebase-apply +' + +test_expect_success 'rebase --merge --quit' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase --merge master && + test_path_is_dir .git/rebase-merge && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test $(git rev-parse HEAD) = $head_before && + test ! -d .git/rebase-merge +' + test_done From d9f31fbfe98e0e363a3c69aaf6badecc746afe6b Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Fri, 18 Nov 2016 16:58:14 -0800 Subject: [PATCH 0039/1540] pretty: add %(trailers) format for displaying trailers of a commit message Recent patches have expanded on the trailers.c code and we have the builtin commant git-interpret-trailers which can be used to add or modify trailer lines. However, there is no easy way to simply display the trailers of a commit message. Add support for %(trailers) format modifier which will use the trailer_info_get() calls to read trailers in an identical way as git interpret-trailers does. Use a long format option instead of a short name so that future work can more easily unify ref-filter and pretty formats. Add documentation and tests for the same. Signed-off-by: Jacob Keller Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 2 ++ pretty.c | 17 +++++++++++++++++ t/t4205-log-pretty-formats.sh | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 3bcee2ddb1244c..47b286b33e4edd 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -199,6 +199,8 @@ endif::git-rev-list[] than given and there are spaces on its left, use those spaces - '%><()', '%><|()': similar to '% <()', '%<|()' respectively, but padding both sides (i.e. the text is centered) +-%(trailers): display the trailers of the body as interpreted by + linkgit:git-interpret-trailers[1] NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --git a/pretty.c b/pretty.c index 37b2c3b1f9954d..5e683830d9d66a 100644 --- a/pretty.c +++ b/pretty.c @@ -10,6 +10,7 @@ #include "color.h" #include "reflog-walk.h" #include "gpg-interface.h" +#include "trailer.h" static char *user_format; static struct cmt_fmt_map { @@ -889,6 +890,16 @@ const char *format_subject(struct strbuf *sb, const char *msg, return msg; } +static void format_trailers(struct strbuf *sb, const char *msg) +{ + struct trailer_info info; + + trailer_info_get(&info, msg); + strbuf_add(sb, info.trailer_start, + info.trailer_end - info.trailer_start); + trailer_info_release(&info); +} + static void parse_commit_message(struct format_commit_context *c) { const char *msg = c->message + c->message_off; @@ -1292,6 +1303,12 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, msg + c->body_off); return 1; } + + if (starts_with(placeholder, "(trailers)")) { + format_trailers(sb, msg + c->subject_off); + return strlen("(trailers)"); + } + return 0; /* unknown placeholder */ } diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index f5435fd250baf7..21eb8c8587f2b4 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -535,4 +535,30 @@ test_expect_success 'clean log decoration' ' test_cmp expected actual1 ' +cat >trailers < +Acked-by: A U Thor +[ v2 updated patch description ] +Signed-off-by: A U Thor +EOF + +test_expect_success 'pretty format %(trailers) shows trailers' ' + echo "Some contents" >trailerfile && + git add trailerfile && + git commit -F - <<-EOF && + trailers: this commit message has trailers + + This commit is a test commit with trailers at the end. We parse this + message and display the trailers using %bT + + $(cat trailers) + EOF + git log --no-walk --pretty="%(trailers)" >actual && + cat >expect <<-EOF && + $(cat trailers) + + EOF + test_cmp expect actual +' + test_done From b1d31c8954f9c21b275f4fb7d872414b564c201c Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Fri, 18 Nov 2016 16:58:15 -0800 Subject: [PATCH 0040/1540] ref-filter: add support to display trailers as part of contents Add %(trailers) and %(contents:trailers) to display the trailers as interpreted by trailer_info_get. Update documentation and add a test for the new feature. Signed-off-by: Jacob Keller Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 2 ++ ref-filter.c | 22 +++++++++++++++++++++- t/t6300-for-each-ref.sh | 26 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index f57e69bc83e33e..e5807eede787de 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -165,6 +165,8 @@ 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. The optional GPG signature is `contents:signature`. The first `N` lines of the message is obtained using `contents:lines=N`. +Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1] +are obtained as 'contents:trailers'. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`). diff --git a/ref-filter.c b/ref-filter.c index d4c2931f3aab14..b6f1bb73ed3741 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -13,6 +13,7 @@ #include "utf8.h" #include "git-compat-util.h" #include "version.h" +#include "trailer.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; @@ -40,7 +41,7 @@ static struct used_atom { enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT } remote_ref; struct { - enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB } option; + enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option; unsigned int nlines; } contents; enum { O_FULL, O_SHORT } objectname; @@ -85,6 +86,13 @@ static void subject_atom_parser(struct used_atom *atom, const char *arg) atom->u.contents.option = C_SUB; } +static void trailers_atom_parser(struct used_atom *atom, const char *arg) +{ + if (arg) + die(_("%%(trailers) does not take arguments")); + atom->u.contents.option = C_TRAILERS; +} + static void contents_atom_parser(struct used_atom *atom, const char *arg) { if (!arg) @@ -95,6 +103,8 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg) atom->u.contents.option = C_SIG; else if (!strcmp(arg, "subject")) atom->u.contents.option = C_SUB; + else if (!strcmp(arg, "trailers")) + atom->u.contents.option = C_TRAILERS; else if (skip_prefix(arg, "lines=", &arg)) { atom->u.contents.option = C_LINES; if (strtoul_ui(arg, 10, &atom->u.contents.nlines)) @@ -194,6 +204,7 @@ static struct { { "creatordate", FIELD_TIME }, { "subject", FIELD_STR, subject_atom_parser }, { "body", FIELD_STR, body_atom_parser }, + { "trailers", FIELD_STR, trailers_atom_parser }, { "contents", FIELD_STR, contents_atom_parser }, { "upstream", FIELD_STR, remote_ref_atom_parser }, { "push", FIELD_STR, remote_ref_atom_parser }, @@ -785,6 +796,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj name++; if (strcmp(name, "subject") && strcmp(name, "body") && + strcmp(name, "trailers") && !starts_with(name, "contents")) continue; if (!subpos) @@ -808,6 +820,14 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj /* Size is the length of the message after removing the signature */ append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines); v->s = strbuf_detach(&s, NULL); + } else if (atom->u.contents.option == C_TRAILERS) { + struct trailer_info info; + + /* Search for trailer info */ + trailer_info_get(&info, subpos); + v->s = xmemdupz(info.trailer_start, + info.trailer_end - info.trailer_start); + trailer_info_release(&info); } else if (atom->u.contents.option == C_BARE) v->s = xstrdup(subpos); } diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 19a2823025e794..eb4bac0fe47785 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -553,4 +553,30 @@ test_expect_success 'Verify sort with multiple keys' ' refs/tags/bogo refs/tags/master > actual && test_cmp expected actual ' + +cat >trailers < +Signed-off-by: A U Thor +EOF + +test_expect_success 'basic atom: head contents:trailers' ' + echo "Some contents" > two && + git add two && + git commit -F - <<-EOF && + trailers: this commit message has trailers + + Some message contents + + $(cat trailers) + EOF + git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual && + sanitize_pgp actual.clean && + # git for-each-ref ends with a blank line + cat >expect <<-EOF && + $(cat trailers) + + EOF + test_cmp expect actual.clean +' + test_done From 5a046c5267dd727acd64ed84cc902465c5aad5d9 Mon Sep 17 00:00:00 2001 From: Rogier Goossens Date: Sat, 19 Mar 2016 19:32:16 +0100 Subject: [PATCH 0041/1540] gitk: Add a 'rename' option to the branch context menu Signed-off-by: Rogier Goossens Signed-off-by: Paul Mackerras --- gitk | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/gitk b/gitk index 805a1c703040d4..84b49bc83319de 100755 --- a/gitk +++ b/gitk @@ -2664,6 +2664,7 @@ proc makewindow {} { set headctxmenu .headctxmenu makemenu $headctxmenu { {mc "Check out this branch" command cobranch} + {mc "Rename this branch" command mvbranch} {mc "Remove this branch" command rmbranch} {mc "Copy branch name" command {clipboard clear; clipboard append $headmenuhead}} } @@ -9452,26 +9453,58 @@ proc wrcomcan {} { } proc mkbranch {} { - global rowmenuid mkbrtop NS + global NS rowmenuid + + set top .branchdialog + + set val(name) "" + set val(id) $rowmenuid + set val(command) [list mkbrgo $top] + + set ui(title) [mc "Create branch"] + set ui(accept) [mc "Create"] + + branchdia $top val ui +} + +proc mvbranch {} { + global NS + global headmenuid headmenuhead + + set top .branchdialog + + set val(name) $headmenuhead + set val(id) $headmenuid + set val(command) [list mvbrgo $top $headmenuhead] + + set ui(title) [mc "Rename branch %s" $headmenuhead] + set ui(accept) [mc "Rename"] + + branchdia $top val ui +} + +proc branchdia {top valvar uivar} { + global NS + upvar $valvar val $uivar ui - set top .makebranch catch {destroy $top} ttk_toplevel $top make_transient $top . - ${NS}::label $top.title -text [mc "Create new branch"] + ${NS}::label $top.title -text $ui(title) grid $top.title - -pady 10 ${NS}::label $top.id -text [mc "ID:"] ${NS}::entry $top.sha1 -width 40 - $top.sha1 insert 0 $rowmenuid + $top.sha1 insert 0 $val(id) $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w ${NS}::label $top.nlab -text [mc "Name:"] ${NS}::entry $top.name -width 40 + $top.name insert 0 $val(name) grid $top.nlab $top.name -sticky w ${NS}::frame $top.buts - ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] + ${NS}::button $top.buts.go -text $ui(accept) -command $val(command) ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" - bind $top [list mkbrgo $top] + bind $top $val(command) bind $top "catch {destroy $top}" grid $top.buts.go $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a @@ -9526,6 +9559,46 @@ proc mkbrgo {top} { } } +proc mvbrgo {top prevname} { + global headids idheads mainhead mainheadid + + set name [$top.name get] + set id [$top.sha1 get] + set cmdargs {} + if {$name eq $prevname} { + catch {destroy $top} + return + } + if {$name eq {}} { + error_popup [mc "Please specify a new name for the branch"] $top + return + } + catch {destroy $top} + lappend cmdargs -m $prevname $name + nowbusy renamebranch + update + if {[catch { + eval exec git branch $cmdargs + } err]} { + notbusy renamebranch + error_popup $err + } else { + notbusy renamebranch + removehead $id $prevname + removedhead $id $prevname + set headids($name) $id + lappend idheads($id) $name + addedhead $id $name + if {$prevname eq $mainhead} { + set mainhead $name + set mainheadid $id + } + redrawtags $id + dispneartags 0 + run refill_reflist + } +} + proc exec_citool {tool_args {baseid {}}} { global commitinfo env @@ -9756,15 +9829,16 @@ proc headmenu {x y id head} { stopfinding set headmenuid $id set headmenuhead $head - set state normal + array set state {0 normal 1 normal 2 normal} if {[string match "remotes/*" $head]} { - set state disabled + array set state {0 disabled 1 disabled 2 disabled} } if {$head eq $mainhead} { - set state disabled + array set state {0 disabled 2 disabled} + } + foreach i {0 1 2} { + $headctxmenu entryconfigure $i -state $state($i) } - $headctxmenu entryconfigure 0 -state $state - $headctxmenu entryconfigure 1 -state $state tk_popup $headctxmenu $x $y } From 02e6a0601b846d197e062f5efdf29fdcef54d54c Mon Sep 17 00:00:00 2001 From: Rogier Goossens Date: Sat, 19 Mar 2016 19:33:03 +0100 Subject: [PATCH 0042/1540] gitk: Allow checking out a remote branch Git allows checking out remote branches, creating a local tracking branch in the process. Allow gitk to do this as well, provided a local branch of the same name does not yet exist. Signed-off-by: Rogier Goossens Signed-off-by: Paul Mackerras --- gitk | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/gitk b/gitk index 84b49bc83319de..dc75c9772b15d8 100755 --- a/gitk +++ b/gitk @@ -9824,14 +9824,18 @@ proc readresetstat {fd} { # context menu for a head proc headmenu {x y id head} { - global headmenuid headmenuhead headctxmenu mainhead + global headmenuid headmenuhead headctxmenu mainhead headids stopfinding set headmenuid $id set headmenuhead $head array set state {0 normal 1 normal 2 normal} if {[string match "remotes/*" $head]} { - array set state {0 disabled 1 disabled 2 disabled} + set localhead [string range $head [expr [string last / $head] + 1] end] + if {[info exists headids($localhead)]} { + set state(0) disabled + } + array set state {1 disabled 2 disabled} } if {$head eq $mainhead} { array set state {0 disabled 2 disabled} @@ -9847,11 +9851,27 @@ proc cobranch {} { global showlocalchanges # check the tree is clean first?? + set newhead $headmenuhead + set command [list | git checkout] + if {[string match "remotes/*" $newhead]} { + set remote $newhead + set newhead [string range $newhead [expr [string last / $newhead] + 1] end] + # The following check is redundant - the menu option should + # be disabled to begin with... + if {[info exists headids($newhead)]} { + error_popup [mc "A local branch named %s exists already" $newhead] + return + } + lappend command -b $newhead --track $remote + } else { + lappend command $newhead + } + lappend command 2>@1 nowbusy checkout [mc "Checking out"] update dohidelocalchanges if {[catch { - set fd [open [list | git checkout $headmenuhead 2>@1] r] + set fd [open $command r] } err]} { notbusy checkout error_popup $err @@ -9859,12 +9879,12 @@ proc cobranch {} { dodiffindex } } else { - filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid] + filerun $fd [list readcheckoutstat $fd $newhead $headmenuid] } } proc readcheckoutstat {fd newhead newheadid} { - global mainhead mainheadid headids showlocalchanges progresscoords + global mainhead mainheadid headids idheads showlocalchanges progresscoords global viewmainheadid curview if {[gets $fd line] >= 0} { @@ -9879,8 +9899,14 @@ proc readcheckoutstat {fd newhead newheadid} { notbusy checkout if {[catch {close $fd} err]} { error_popup $err + return } set oldmainid $mainheadid + if {! [info exists headids($newhead)]} { + set headids($newhead) $newheadid + lappend idheads($newheadid) $newhead + addedhead $newheadid $newhead + } set mainhead $newhead set mainheadid $newheadid set viewmainheadid($curview) $newheadid From 7f00f4c0de4f2ceef43b19e9465d8e8c0977caac Mon Sep 17 00:00:00 2001 From: Rogier Goossens Date: Sun, 27 Mar 2016 09:21:01 +0200 Subject: [PATCH 0043/1540] gitk: Include commit title in branch dialog Hi, I made another branch dialog related change, included in this message. It applies on top of my other two patches. Rogier. ------- 8< ------------------- 8< -------------- Only the SHA1 was included. It's convenient to have the title mentioned as well. Signed-off-by: Rogier Goossens Signed-off-by: Paul Mackerras --- gitk | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gitk b/gitk index dc75c9772b15d8..413711e4d4f347 100755 --- a/gitk +++ b/gitk @@ -9484,7 +9484,7 @@ proc mvbranch {} { } proc branchdia {top valvar uivar} { - global NS + global NS commitinfo upvar $valvar val $uivar ui catch {destroy $top} @@ -9497,6 +9497,11 @@ proc branchdia {top valvar uivar} { $top.sha1 insert 0 $val(id) $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w + ${NS}::entry $top.head -width 60 + $top.head insert 0 [lindex $commitinfo($val(id)) 0] + $top.head conf -state readonly + grid x $top.head -sticky ew + grid columnconfigure $top 1 -weight 1 ${NS}::label $top.nlab -text [mc "Name:"] ${NS}::entry $top.name -width 40 $top.name insert 0 $val(name) From 944e1c8ede45b2d282c7431d7dcdc90ee47cbdd5 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Thu, 5 May 2016 17:46:32 +0000 Subject: [PATCH 0044/1540] gitk: Makefile: create install bin directory Force creation of destination bin directory. Without this, gitk would fail to install if this directory didn't already exist. Signed-off-by: Vasco Almeida Signed-off-by: Paul Mackerras --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5acdc900abdfb3..5bdd52a6ebfa72 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ endif all:: gitk-wish $(ALL_MSGFILES) install:: all + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -m 755 gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(msgsdir_SQ)' $(foreach p,$(ALL_MSGFILES), $(INSTALL) -m 644 $p '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true From 2239d07f5a12353c1e3f65d8297e42b16f58bec8 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 11 May 2016 20:01:33 +0000 Subject: [PATCH 0045/1540] gitk: Add Portuguese translation Signed-off-by: Vasco Almeida Signed-off-by: Paul Mackerras --- po/pt_pt.po | 1376 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1376 insertions(+) create mode 100644 po/pt_pt.po diff --git a/po/pt_pt.po b/po/pt_pt.po new file mode 100644 index 00000000000000..a018ef91148746 --- /dev/null +++ b/po/pt_pt.po @@ -0,0 +1,1376 @@ +# Portuguese translations for gitk package. +# Copyright (C) 2016 Paul Mackerras +# This file is distributed under the same license as the gitk package. +# Vasco Almeida , 2016. +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-15 16:52+0000\n" +"PO-Revision-Date: 2016-05-06 15:35+0000\n" +"Last-Translator: Vasco Almeida \n" +"Language-Team: Portuguese\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" + +#: gitk:140 +msgid "Couldn't get list of unmerged files:" +msgstr "Não foi possível obter lista de ficheiros não integrados:" + +#: gitk:212 gitk:2399 +msgid "Color words" +msgstr "Colorir palavras" + +#: gitk:217 gitk:2399 gitk:8239 gitk:8272 +msgid "Markup words" +msgstr "Marcar palavras" + +#: gitk:324 +msgid "Error parsing revisions:" +msgstr "Erro ao analisar revisões:" + +#: gitk:380 +msgid "Error executing --argscmd command:" +msgstr "Erro ao executar o comando de --argscmd:" + +#: gitk:393 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por " +"integrar." + +#: gitk:396 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por " +"integrar ao nível de ficheiro." + +#: gitk:418 gitk:566 +msgid "Error executing git log:" +msgstr "Erro ao executar git log:" + +#: gitk:436 gitk:582 +msgid "Reading" +msgstr "A ler" + +#: gitk:496 gitk:4544 +msgid "Reading commits..." +msgstr "A ler commits..." + +#: gitk:499 gitk:1637 gitk:4547 +msgid "No commits selected" +msgstr "Nenhum commit selecionado" + +#: gitk:1445 gitk:4064 gitk:12469 +msgid "Command line" +msgstr "Linha de comandos" + +#: gitk:1511 +msgid "Can't parse git log output:" +msgstr "Não é possível analisar a saída de git log:" + +#: gitk:1740 +msgid "No commit information available" +msgstr "Não há informação disponível sobre o commit" + +#: gitk:1903 gitk:1932 gitk:4334 gitk:9702 gitk:11274 gitk:11554 +msgid "OK" +msgstr "OK" + +#: gitk:1934 gitk:4336 gitk:9215 gitk:9294 gitk:9424 gitk:9473 gitk:9704 +#: gitk:11275 gitk:11555 +msgid "Cancel" +msgstr "Cancelar" + +#: gitk:2083 +msgid "&Update" +msgstr "At&ualizar" + +#: gitk:2084 +msgid "&Reload" +msgstr "&Recarregar" + +#: gitk:2085 +msgid "Reread re&ferences" +msgstr "Reler re&ferências" + +#: gitk:2086 +msgid "&List references" +msgstr "&Listar referências" + +#: gitk:2088 +msgid "Start git &gui" +msgstr "Iniciar git &gui" + +#: gitk:2090 +msgid "&Quit" +msgstr "&Sair" + +#: gitk:2082 +msgid "&File" +msgstr "&Ficheiro" + +#: gitk:2094 +msgid "&Preferences" +msgstr "&Preferências" + +#: gitk:2093 +msgid "&Edit" +msgstr "&Editar" + +#: gitk:2098 +msgid "&New view..." +msgstr "&Nova vista..." + +#: gitk:2099 +msgid "&Edit view..." +msgstr "&Editar vista..." + +#: gitk:2100 +msgid "&Delete view" +msgstr "Elimina&r vista" + +#: gitk:2102 +msgid "&All files" +msgstr "&Todos os ficheiros" + +#: gitk:2097 +msgid "&View" +msgstr "&Ver" + +#: gitk:2107 gitk:2117 +msgid "&About gitk" +msgstr "&Sobre gitk" + +#: gitk:2108 gitk:2122 +msgid "&Key bindings" +msgstr "&Atalhos" + +#: gitk:2106 gitk:2121 +msgid "&Help" +msgstr "&Ajuda" + +#: gitk:2199 gitk:8671 +msgid "SHA1 ID:" +msgstr "ID SHA1:" + +#: gitk:2243 +msgid "Row" +msgstr "Linha" + +#: gitk:2281 +msgid "Find" +msgstr "Procurar" + +#: gitk:2309 +msgid "commit" +msgstr "commit" + +#: gitk:2313 gitk:2315 gitk:4706 gitk:4729 gitk:4753 gitk:6774 gitk:6846 +#: gitk:6931 +msgid "containing:" +msgstr "contendo:" + +#: gitk:2316 gitk:3545 gitk:3550 gitk:4782 +msgid "touching paths:" +msgstr "altera os caminhos:" + +#: gitk:2317 gitk:4796 +msgid "adding/removing string:" +msgstr "adiciona/remove a cadeia:" + +#: gitk:2318 gitk:4798 +msgid "changing lines matching:" +msgstr "altera linhas com:" + +#: gitk:2327 gitk:2329 gitk:4785 +msgid "Exact" +msgstr "Exato" + +#: gitk:2329 gitk:4873 gitk:6742 +msgid "IgnCase" +msgstr "IgnMaiúsculas" + +#: gitk:2329 gitk:4755 gitk:4871 gitk:6738 +msgid "Regexp" +msgstr "Expr. regular" + +#: gitk:2331 gitk:2332 gitk:4893 gitk:4923 gitk:4930 gitk:6867 gitk:6935 +msgid "All fields" +msgstr "Todos os campos" + +#: gitk:2332 gitk:4890 gitk:4923 gitk:6805 +msgid "Headline" +msgstr "Cabeçalho" + +#: gitk:2333 gitk:4890 gitk:6805 gitk:6935 gitk:7408 +msgid "Comments" +msgstr "Comentários" + +#: gitk:2333 gitk:4890 gitk:4895 gitk:4930 gitk:6805 gitk:7343 gitk:8849 +#: gitk:8864 +msgid "Author" +msgstr "Autor" + +#: gitk:2333 gitk:4890 gitk:6805 gitk:7345 +msgid "Committer" +msgstr "Committer" + +#: gitk:2367 +msgid "Search" +msgstr "Pesquisar" + +#: gitk:2375 +msgid "Diff" +msgstr "Diff" + +#: gitk:2377 +msgid "Old version" +msgstr "Versão antiga" + +#: gitk:2379 +msgid "New version" +msgstr "Versão nova" + +#: gitk:2382 +msgid "Lines of context" +msgstr "Linhas de contexto" + +#: gitk:2392 +msgid "Ignore space change" +msgstr "Ignorar espaços" + +#: gitk:2396 gitk:2398 gitk:7978 gitk:8225 +msgid "Line diff" +msgstr "Diff de linha" + +#: gitk:2463 +msgid "Patch" +msgstr "Patch" + +#: gitk:2465 +msgid "Tree" +msgstr "Árvore" + +#: gitk:2635 gitk:2656 +msgid "Diff this -> selected" +msgstr "Diff este -> seleção" + +#: gitk:2636 gitk:2657 +msgid "Diff selected -> this" +msgstr "Diff seleção -> este" + +#: gitk:2637 gitk:2658 +msgid "Make patch" +msgstr "Gerar patch" + +#: gitk:2638 gitk:9273 +msgid "Create tag" +msgstr "Criar tag" + +#: gitk:2639 +msgid "Copy commit summary" +msgstr "Copiar sumário do commit" + +#: gitk:2640 gitk:9404 +msgid "Write commit to file" +msgstr "Escrever commit num ficheiro" + +#: gitk:2641 gitk:9461 +msgid "Create new branch" +msgstr "Criar novo ramo" + +#: gitk:2642 +msgid "Cherry-pick this commit" +msgstr "Efetuar cherry-pick deste commit" + +#: gitk:2643 +msgid "Reset HEAD branch to here" +msgstr "Repor ramo HEAD para aqui" + +#: gitk:2644 +msgid "Mark this commit" +msgstr "Marcar este commit" + +#: gitk:2645 +msgid "Return to mark" +msgstr "Voltar à marca" + +#: gitk:2646 +msgid "Find descendant of this and mark" +msgstr "Encontrar descendeste deste e da marca" + +#: gitk:2647 +msgid "Compare with marked commit" +msgstr "Comparar com o commit marcado" + +#: gitk:2648 gitk:2659 +msgid "Diff this -> marked commit" +msgstr "Diff este -> commit marcado" + +#: gitk:2649 gitk:2660 +msgid "Diff marked commit -> this" +msgstr "Diff commit marcado -> este" + +#: gitk:2650 +msgid "Revert this commit" +msgstr "Reverter este commit" + +#: gitk:2666 +msgid "Check out this branch" +msgstr "Extrair este ramo" + +#: gitk:2667 +msgid "Remove this branch" +msgstr "Remover este ramo" + +#: gitk:2668 +msgid "Copy branch name" +msgstr "Copiar nome do ramo" + +#: gitk:2675 +msgid "Highlight this too" +msgstr "Realçar este também" + +#: gitk:2676 +msgid "Highlight this only" +msgstr "Realçar apenas este" + +#: gitk:2677 +msgid "External diff" +msgstr "Diff externo" + +#: gitk:2678 +msgid "Blame parent commit" +msgstr "Culpar commit pai" + +#: gitk:2679 +msgid "Copy path" +msgstr "Copiar caminho" + +#: gitk:2686 +msgid "Show origin of this line" +msgstr "Mostrar origem deste ficheiro" + +#: gitk:2687 +msgid "Run git gui blame on this line" +msgstr "Executar git gui blame sobre esta linha" + +#: gitk:3031 +msgid "About gitk" +msgstr "Sobre gitk" + +#: gitk:3033 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright © 2005-2014 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - um visualizador de commits do git\n" +"\n" +"Copyright © 2005-2014 Paul Mackerras\n" +"\n" +"Use e redistribua sob os termos da GNU General Public License" + +#: gitk:3041 gitk:3108 gitk:9890 +msgid "Close" +msgstr "Fechar" + +#: gitk:3062 +msgid "Gitk key bindings" +msgstr "Atalhos do gitk" + +#: gitk:3065 +msgid "Gitk key bindings:" +msgstr "Atalhos do gitk:" + +#: gitk:3067 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tSair" + +#: gitk:3068 +#, tcl-format +msgid "<%s-W>\t\tClose window" +msgstr "<%s-W>\t\tFechar janela" + +#: gitk:3069 +msgid "\t\tMove to first commit" +msgstr "\t\tMover para o primeiro commit" + +#: gitk:3070 +msgid "\t\tMove to last commit" +msgstr "\t\tMover para o último commit" + +#: gitk:3071 +msgid ", p, k\tMove up one commit" +msgstr ", p, k\tMover para o commit acima" + +#: gitk:3072 +msgid ", n, j\tMove down one commit" +msgstr ", n, j\tMover para o commit abaixo" + +#: gitk:3073 +msgid ", z, h\tGo back in history list" +msgstr ", z, h\tRecuar no histórico" + +#: gitk:3074 +msgid ", x, l\tGo forward in history list" +msgstr ", x, l\tAvançar no histórico" + +#: gitk:3075 +#, tcl-format +msgid "<%s-n>\tGo to n-th parent of current commit in history list" +msgstr "<%s-n>\tIr para o n-ésimo pai do commit atual no histórico" + +#: gitk:3076 +msgid "\tMove up one page in commit list" +msgstr "\tMover a lista de commits uma página para cima" + +#: gitk:3077 +msgid "\tMove down one page in commit list" +msgstr "\tMover a lista de commits uma página para baixo" + +#: gitk:3078 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tDeslocar para o topo da lista" + +#: gitk:3079 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tDeslocar para o fim da lista" + +#: gitk:3080 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Cima>\tDeslocar a lista de commits uma linha para cima" + +#: gitk:3081 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Baixo>\tDeslocar a lista de commits uma linha para baixo" + +#: gitk:3082 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tDeslocar a lista de commits uma página para cima" + +#: gitk:3083 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tDeslocar a lista de commits uma página para baixo" + +#: gitk:3084 +msgid "\tFind backwards (upwards, later commits)" +msgstr "\tProcurar para trás (para cima, commits posteriores)" + +#: gitk:3085 +msgid "\tFind forwards (downwards, earlier commits)" +msgstr "\tProcurar para a frente (para baixo, commits anteriores)" + +#: gitk:3086 +msgid ", b\tScroll diff view up one page" +msgstr ", b\tDeslocar vista diff uma página para cima" + +#: gitk:3087 +msgid "\tScroll diff view up one page" +msgstr "\tDeslocar vista diff uma página para cima" + +#: gitk:3088 +msgid "\t\tScroll diff view down one page" +msgstr "\tDeslocar vista diff uma página para baixo" + +#: gitk:3089 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tDeslocar vista diff 18 linhas para cima" + +#: gitk:3090 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tDeslocar vista diff 18 linhas para baixo" + +#: gitk:3091 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tProcurar" + +#: gitk:3092 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tMover para a ocorrência seguinte" + +#: gitk:3093 +msgid "\tMove to next find hit" +msgstr "\tMover para a ocorrência seguinte" + +#: gitk:3094 +msgid "g\t\tGo to commit" +msgstr "g\t\tIr para o commit" + +#: gitk:3095 +msgid "/\t\tFocus the search box" +msgstr "/\t\tFocar a caixa de pesquisa" + +#: gitk:3096 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tMover para a ocorrência anterior" + +#: gitk:3097 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tDeslocar vista diff para o ficheiro seguinte" + +#: gitk:3098 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tProcurar pela ocorrência seguinte na vista diff" + +#: gitk:3099 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tProcurar pela ocorrência anterior na vista diff" + +#: gitk:3100 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tAumentar o tamanho da letra" + +#: gitk:3101 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-mais>\tAumentar o tamanho da letra" + +#: gitk:3102 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tDiminuir o tamanho da letra" + +#: gitk:3103 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-menos>\tDiminuir o tamanho da letra" + +#: gitk:3104 +msgid "\t\tUpdate" +msgstr "\t\tAtualizar" + +#: gitk:3569 gitk:3578 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Erro ao criar ficheiro temporário %s:" + +#: gitk:3591 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Erro ao obter \"%s\" de %s:" + +#: gitk:3654 +msgid "command failed:" +msgstr "o comando falhou:" + +#: gitk:3803 +msgid "No such commit" +msgstr "Commit inexistente" + +#: gitk:3817 +msgid "git gui blame: command failed:" +msgstr "git gui blame: o comando falhou:" + +#: gitk:3848 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Não foi possível ler a cabeça de integração: %s" + +#: gitk:3856 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Erro ao ler o índice: %s" + +#: gitk:3881 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Não foi possível iniciar git blame: %s" + +#: gitk:3884 gitk:6773 +msgid "Searching" +msgstr "A procurar" + +#: gitk:3916 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Erro ao executar git blame: %s" + +#: gitk:3944 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Essa linha provém do commit %s, que não está nesta vista" + +#: gitk:3958 +msgid "External diff viewer failed:" +msgstr "Visualizador diff externo falhou:" + +#: gitk:4062 +msgid "All files" +msgstr "Todos os ficheiros" + +#: gitk:4086 +msgid "View" +msgstr "Vista" + +#: gitk:4089 +msgid "Gitk view definition" +msgstr "Definição de vistas do gitk" + +#: gitk:4093 +msgid "Remember this view" +msgstr "Recordar esta vista" + +#: gitk:4094 +msgid "References (space separated list):" +msgstr "Referências (lista separada por espaço):" + +#: gitk:4095 +msgid "Branches & tags:" +msgstr "Ramos e tags:" + +#: gitk:4096 +msgid "All refs" +msgstr "Todas as referências" + +#: gitk:4097 +msgid "All (local) branches" +msgstr "Todos os ramos (locais)" + +#: gitk:4098 +msgid "All tags" +msgstr "Todas as tags" + +#: gitk:4099 +msgid "All remote-tracking branches" +msgstr "Todos os ramos remotos de monitorização" + +#: gitk:4100 +msgid "Commit Info (regular expressions):" +msgstr "Informação Sobre o Commit (expressões regulares):" + +#: gitk:4101 +msgid "Author:" +msgstr "Autor:" + +#: gitk:4102 +msgid "Committer:" +msgstr "Committer:" + +#: gitk:4103 +msgid "Commit Message:" +msgstr "Mensagem de Commit:" + +#: gitk:4104 +msgid "Matches all Commit Info criteria" +msgstr "Corresponde a todos os critérios da Informação Sobre o Commit" + +#: gitk:4105 +msgid "Matches no Commit Info criteria" +msgstr "Não corresponde a nenhum critério da Informação Sobre o Commit" + +#: gitk:4106 +msgid "Changes to Files:" +msgstr "Alterações nos Ficheiros:" + +#: gitk:4107 +msgid "Fixed String" +msgstr "Cadeia Fixa" + +#: gitk:4108 +msgid "Regular Expression" +msgstr "Expressão Regular" + +#: gitk:4109 +msgid "Search string:" +msgstr "Procurar pela cadeia:" + +#: gitk:4110 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Datas de Commit (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:4111 +msgid "Since:" +msgstr "Desde:" + +#: gitk:4112 +msgid "Until:" +msgstr "Até:" + +#: gitk:4113 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):" + +#: gitk:4114 +msgid "Number to show:" +msgstr "Número a mostrar:" + +#: gitk:4115 +msgid "Number to skip:" +msgstr "Número a ignorar:" + +#: gitk:4116 +msgid "Miscellaneous options:" +msgstr "Opções diversas:" + +#: gitk:4117 +msgid "Strictly sort by date" +msgstr "Ordenar estritamente pela data" + +#: gitk:4118 +msgid "Mark branch sides" +msgstr "Marcar lado dos ramos" + +#: gitk:4119 +msgid "Limit to first parent" +msgstr "Restringir ao primeiro pai" + +#: gitk:4120 +msgid "Simple history" +msgstr "Histórico simples" + +#: gitk:4121 +msgid "Additional arguments to git log:" +msgstr "Argumentos adicionais ao git log:" + +#: gitk:4122 +msgid "Enter files and directories to include, one per line:" +msgstr "Introduza ficheiros e diretórios para incluir, um por linha:" + +#: gitk:4123 +msgid "Command to generate more commits to include:" +msgstr "Comando para gerar mais commits para incluir:" + +#: gitk:4247 +msgid "Gitk: edit view" +msgstr "Gitk: editar vista" + +#: gitk:4255 +msgid "-- criteria for selecting revisions" +msgstr "-- critério para selecionar revisões" + +#: gitk:4260 +msgid "View Name" +msgstr "Nome da Vista" + +#: gitk:4335 +msgid "Apply (F5)" +msgstr "Aplicar (F5)" + +#: gitk:4373 +msgid "Error in commit selection arguments:" +msgstr "Erro nos argumentos de seleção de commits:" + +#: gitk:4428 gitk:4481 gitk:4943 gitk:4957 gitk:6227 gitk:12410 gitk:12411 +msgid "None" +msgstr "Nenhum" + +#: gitk:5040 gitk:5045 +msgid "Descendant" +msgstr "Descendente" + +#: gitk:5041 +msgid "Not descendant" +msgstr "Não descendente" + +#: gitk:5048 gitk:5053 +msgid "Ancestor" +msgstr "Antecessor" + +#: gitk:5049 +msgid "Not ancestor" +msgstr "Não antecessor" + +#: gitk:5343 +msgid "Local changes checked in to index but not committed" +msgstr "Alterações locais preparadas no índice mas não submetidas" + +#: gitk:5379 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Alterações locais não submetidas, não preparadas no índice" + +#: gitk:7153 +msgid "and many more" +msgstr "e muitos mais" + +#: gitk:7156 +msgid "many" +msgstr "muitos" + +#: gitk:7347 +msgid "Tags:" +msgstr "Tags:" + +#: gitk:7364 gitk:7370 gitk:8844 +msgid "Parent" +msgstr "Pai" + +#: gitk:7375 +msgid "Child" +msgstr "Filho" + +#: gitk:7384 +msgid "Branch" +msgstr "Ramo" + +#: gitk:7387 +msgid "Follows" +msgstr "Sucede" + +#: gitk:7390 +msgid "Precedes" +msgstr "Precede" + +#: gitk:7985 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Erro ao obter diferenças: %s" + +#: gitk:8669 +msgid "Goto:" +msgstr "Ir para:" + +#: gitk:8690 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "O id SHA1 abreviado %s é ambíguo" + +#: gitk:8697 +#, tcl-format +msgid "Revision %s is not known" +msgstr "A revisão %s não é conhecida" + +#: gitk:8707 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "O id SHA1 %s não é conhecido" + +#: gitk:8709 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "A revisão %s não se encontra na vista atual" + +#: gitk:8851 gitk:8866 +msgid "Date" +msgstr "Data" + +#: gitk:8854 +msgid "Children" +msgstr "Filhos" + +#: gitk:8917 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Repor o ramo %s para aqui" + +#: gitk:8919 +msgid "Detached head: can't reset" +msgstr "Cabeça destacada: não é possível repor" + +#: gitk:9024 gitk:9030 +msgid "Skipping merge commit " +msgstr "A ignorar commit de integração " + +#: gitk:9039 gitk:9044 +msgid "Error getting patch ID for " +msgstr "Erro ao obter ID de patch de " + +#: gitk:9040 gitk:9045 +msgid " - stopping\n" +msgstr " - a interromper\n" + +#: gitk:9050 gitk:9053 gitk:9061 gitk:9075 gitk:9084 +msgid "Commit " +msgstr "Commit " + +#: gitk:9054 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" é o mesmo patch que\n" +" " + +#: gitk:9062 +msgid "" +" differs from\n" +" " +msgstr "" +" difere de\n" +" " + +#: gitk:9064 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"Diferença dos commits:\n" +"\n" + +#: gitk:9076 gitk:9085 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " tem %s filhos - a interromper\n" + +#: gitk:9104 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Erro ao escrever commit no ficheiro: %s" + +#: gitk:9110 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Erro ao calcular as diferenças dos commits: %s" + +#: gitk:9156 +msgid "Top" +msgstr "Topo" + +#: gitk:9157 +msgid "From" +msgstr "De" + +#: gitk:9162 +msgid "To" +msgstr "Para" + +#: gitk:9186 +msgid "Generate patch" +msgstr "Gerar patch" + +#: gitk:9188 +msgid "From:" +msgstr "De:" + +#: gitk:9197 +msgid "To:" +msgstr "Para:" + +#: gitk:9206 +msgid "Reverse" +msgstr "Reverter" + +#: gitk:9208 gitk:9418 +msgid "Output file:" +msgstr "Ficheiro de saída:" + +#: gitk:9214 +msgid "Generate" +msgstr "Gerar" + +#: gitk:9252 +msgid "Error creating patch:" +msgstr "Erro ao criar patch:" + +#: gitk:9275 gitk:9406 gitk:9463 +msgid "ID:" +msgstr "ID:" + +#: gitk:9284 +msgid "Tag name:" +msgstr "Nome da tag:" + +#: gitk:9287 +msgid "Tag message is optional" +msgstr "A mensagem da tag é opcional" + +#: gitk:9289 +msgid "Tag message:" +msgstr "Mensagem da tag:" + +#: gitk:9293 gitk:9472 +msgid "Create" +msgstr "Criar" + +#: gitk:9311 +msgid "No tag name specified" +msgstr "Nenhum nome de tag especificado" + +#: gitk:9315 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "A tag \"%s\" já existe" + +#: gitk:9325 +msgid "Error creating tag:" +msgstr "Erro ao criar tag:" + +#: gitk:9415 +msgid "Command:" +msgstr "Comando:" + +#: gitk:9423 +msgid "Write" +msgstr "Escrever" + +#: gitk:9441 +msgid "Error writing commit:" +msgstr "Erro ao escrever commit:" + +#: gitk:9468 +msgid "Name:" +msgstr "Nome:" + +#: gitk:9491 +msgid "Please specify a name for the new branch" +msgstr "Especifique um nome para o novo ramo" + +#: gitk:9496 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "O ramo '%s' já existe. Substituí-lo?" + +#: gitk:9563 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "O commit %s já está incluído no ramo %s -- reaplicá-lo mesmo assim?" + +#: gitk:9568 +msgid "Cherry-picking" +msgstr "A efetuar cherry-pick" + +#: gitk:9577 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"Falha ao efetuar cherry-pick devido a alterações locais no ficheiro '%s'.\n" +"Submeta, empilhe ou reponha as alterações e tente de novo." + +#: gitk:9583 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Falha ao efetuar cherry-pick devido a conflito de integração.\n" +"Deseja executar git citool para resolvê-lo?" + +#: gitk:9599 gitk:9657 +msgid "No changes committed" +msgstr "Não foi submetida nenhum alteração" + +#: gitk:9626 +#, tcl-format +msgid "Commit %s is not included in branch %s -- really revert it?" +msgstr "O commit %s não está incluído no ramo %s -- revertê-lo mesmo assim?" + +#: gitk:9631 +msgid "Reverting" +msgstr "A reverter" + +#: gitk:9639 +#, tcl-format +msgid "" +"Revert failed because of local changes to the following files:%s Please " +"commit, reset or stash your changes and try again." +msgstr "" +"Falha ao reverter devido a alterações locais nos seguintes ficheiros:%s " +"Submeta, empilhe ou reponha as alterações e tente de novo." + +#: gitk:9643 +msgid "" +"Revert failed because of merge conflict.\n" +" Do you wish to run git citool to resolve it?" +msgstr "" +"Falha ao reverter devido a conflito de integração.\n" +"Deseja executar git citool para resolvê-lo?" + +#: gitk:9686 +msgid "Confirm reset" +msgstr "Confirmar reposição" + +#: gitk:9688 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Repor o ramo %s para %s?" + +#: gitk:9690 +msgid "Reset type:" +msgstr "Tipo de reposição:" + +#: gitk:9693 +msgid "Soft: Leave working tree and index untouched" +msgstr "Suave: Deixar a árvore de trabalho e o índice intactos" + +#: gitk:9696 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Misto: Deixar a árvore de trabalho intacta, repor índice" + +#: gitk:9699 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Forte: Repor árvore de trabalho e índice\n" +"(descartar TODAS as alterações locais)" + +#: gitk:9716 +msgid "Resetting" +msgstr "A repor" + +#: gitk:9776 +msgid "Checking out" +msgstr "A extrair" + +#: gitk:9829 +msgid "Cannot delete the currently checked-out branch" +msgstr "Não é possível eliminar o ramo atual extraído" + +#: gitk:9835 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"Os commits no ramo %s não estão presentes em mais nenhum ramo.\n" +"Eliminar o ramo %s mesmo assim?" + +#: gitk:9866 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Tags e cabeças: %s" + +#: gitk:9883 +msgid "Filter" +msgstr "Filtrar" + +#: gitk:10179 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Erro ao ler informação de topologia do commit; a informação do ramo e da tag " +"precedente/seguinte ficará incompleta." + +#: gitk:11156 +msgid "Tag" +msgstr "Tag" + +#: gitk:11160 +msgid "Id" +msgstr "Id" + +#: gitk:11243 +msgid "Gitk font chooser" +msgstr "Escolha de tipo de letra do gitk" + +#: gitk:11260 +msgid "B" +msgstr "B" + +#: gitk:11263 +msgid "I" +msgstr "I" + +#: gitk:11381 +msgid "Commit list display options" +msgstr "Opções de visualização da lista de commits" + +#: gitk:11384 +msgid "Maximum graph width (lines)" +msgstr "Largura máxima do gráfico (linhas)" + +#: gitk:11388 +#, no-tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Largura máxima do gráfico (% do painel)" + +#: gitk:11391 +msgid "Show local changes" +msgstr "Mostrar alterações locais" + +#: gitk:11394 +msgid "Auto-select SHA1 (length)" +msgstr "Selecionar automaticamente SHA1 (largura)" + +#: gitk:11398 +msgid "Hide remote refs" +msgstr "Ocultar referências remotas" + +#: gitk:11402 +msgid "Diff display options" +msgstr "Opções de visualização de diferenças" + +#: gitk:11404 +msgid "Tab spacing" +msgstr "Espaçamento da tabulação" + +#: gitk:11407 +msgid "Display nearby tags/heads" +msgstr "Mostrar tags/cabeças próximas" + +#: gitk:11410 +msgid "Maximum # tags/heads to show" +msgstr "Nº máximo de tags/cabeças a mostrar" + +#: gitk:11413 +msgid "Limit diffs to listed paths" +msgstr "Limitar diferenças aos caminhos listados" + +#: gitk:11416 +msgid "Support per-file encodings" +msgstr "Suportar codificação por cada ficheiro" + +#: gitk:11422 gitk:11569 +msgid "External diff tool" +msgstr "Ferramenta diff externa" + +#: gitk:11423 +msgid "Choose..." +msgstr "Escolher..." + +#: gitk:11428 +msgid "General options" +msgstr "Opções gerais" + +#: gitk:11431 +msgid "Use themed widgets" +msgstr "Usar widgets com estilo" + +#: gitk:11433 +msgid "(change requires restart)" +msgstr "(alteração exige reiniciar)" + +#: gitk:11435 +msgid "(currently unavailable)" +msgstr "(não disponível de momento)" + +#: gitk:11446 +msgid "Colors: press to choose" +msgstr "Cores: pressione para escolher" + +#: gitk:11449 +msgid "Interface" +msgstr "Interface" + +#: gitk:11450 +msgid "interface" +msgstr "interface" + +#: gitk:11453 +msgid "Background" +msgstr "Fundo" + +#: gitk:11454 gitk:11484 +msgid "background" +msgstr "fundo" + +#: gitk:11457 +msgid "Foreground" +msgstr "Primeiro plano" + +#: gitk:11458 +msgid "foreground" +msgstr "primeiro plano" + +#: gitk:11461 +msgid "Diff: old lines" +msgstr "Diff: linhas antigas" + +#: gitk:11462 +msgid "diff old lines" +msgstr "diff linhas antigas" + +#: gitk:11466 +msgid "Diff: new lines" +msgstr "Diff: linhas novas" + +#: gitk:11467 +msgid "diff new lines" +msgstr "diff linhas novas" + +#: gitk:11471 +msgid "Diff: hunk header" +msgstr "Diff: cabeçalho do excerto" + +#: gitk:11473 +msgid "diff hunk header" +msgstr "diff cabeçalho do excerto" + +#: gitk:11477 +msgid "Marked line bg" +msgstr "Fundo da linha marcada" + +#: gitk:11479 +msgid "marked line background" +msgstr "fundo da linha marcada" + +#: gitk:11483 +msgid "Select bg" +msgstr "Selecionar fundo" + +#: gitk:11492 +msgid "Fonts: press to choose" +msgstr "Tipo de letra: pressione para escolher" + +#: gitk:11494 +msgid "Main font" +msgstr "Tipo de letra principal" + +#: gitk:11495 +msgid "Diff display font" +msgstr "Tipo de letra ao mostrar diferenças" + +#: gitk:11496 +msgid "User interface font" +msgstr "Tipo de letra da interface de utilizador" + +#: gitk:11518 +msgid "Gitk preferences" +msgstr "Preferências do gitk" + +#: gitk:11527 +msgid "General" +msgstr "Geral" + +#: gitk:11528 +msgid "Colors" +msgstr "Cores" + +#: gitk:11529 +msgid "Fonts" +msgstr "Tipos de letra" + +#: gitk:11579 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: escolher cor de %s" + +#: gitk:12092 +msgid "" +"Sorry, gitk cannot run with this version of Tcl/Tk.\n" +" Gitk requires at least Tcl/Tk 8.4." +msgstr "" +"Não é possível executar o gitk com esta versão do Tcl/Tk.\n" +"O gitk requer pelo menos Tcl/Tk 8.4." + +#: gitk:12302 +msgid "Cannot find a git repository here." +msgstr "Não foi encontrado nenhum repositório git aqui." + +#: gitk:12349 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Argumento '%s' ambíguo: pode ser uma revisão ou um ficheiro" + +#: gitk:12361 +msgid "Bad arguments to gitk:" +msgstr "Argumentos do gitk incorretos:" From 6e8fda5fd2d30d9e9e83af146b64c5b13ee7f2ef Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Mon, 12 Dec 2016 11:29:21 +1100 Subject: [PATCH 0046/1540] gitk: Use explicit RGB green instead of "lime" Some systems don't recognize "lime" as a color, leading to errors when gitk is run. What we want is a bright green, so use "#00ff00" instead. Signed-off-by: Paul Mackerras --- gitk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gitk b/gitk index 413711e4d4f347..c60f180941f192 100755 --- a/gitk +++ b/gitk @@ -2265,7 +2265,7 @@ proc makewindow {} { set h [expr {[font metrics uifont -linespace] + 2}] set progresscanv .tf.bar.progress canvas $progresscanv -relief sunken -height $h -borderwidth 2 - set progressitem [$progresscanv create rect -1 0 0 $h -fill lime] + set progressitem [$progresscanv create rect -1 0 0 $h -fill "#00ff00"] set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] } @@ -3398,7 +3398,7 @@ set rectmask { 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00}; } -image create bitmap reficon-H -background black -foreground lime \ +image create bitmap reficon-H -background black -foreground "#00ff00" \ -data $rectdata -maskdata $rectmask image create bitmap reficon-o -background black -foreground "#ddddff" \ -data $rectdata -maskdata $rectmask @@ -12293,7 +12293,7 @@ if {[tk windowingsystem] eq "aqua"} { set extdifftool "meld" } -set colors {lime red blue magenta darkgrey brown orange} +set colors {"#00ff00" red blue magenta darkgrey brown orange} if {[tk windowingsystem] eq "win32"} { set uicolor SystemButtonFace set uifgcolor SystemButtonText @@ -12311,12 +12311,12 @@ if {[tk windowingsystem] eq "win32"} { } set diffcolors {red "#00a000" blue} set diffcontext 3 -set mergecolors {red blue lime purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"} +set mergecolors {red blue "#00ff00" purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"} set ignorespace 0 set worddiff "" set markbgcolor "#e0e0ff" -set headbgcolor lime +set headbgcolor "#00ff00" set headfgcolor black set headoutlinecolor black set remotebgcolor #ffddaa @@ -12331,7 +12331,7 @@ set linehoverfgcolor black set linehoveroutlinecolor black set mainheadcirclecolor yellow set workingfilescirclecolor red -set indexcirclecolor lime +set indexcirclecolor "#00ff00" set circlecolors {white blue gray blue blue} set linkfgcolor blue set circleoutlinecolor $fgcolor From d92aa57039cf2e4acae6aedfe4a4d6bf39562763 Mon Sep 17 00:00:00 2001 From: Stefan Dotterweich Date: Sat, 4 Jun 2016 10:47:16 +0200 Subject: [PATCH 0047/1540] gitk: Fix missing commits when using -S or -G When -S or -G is used as a filter option, the resulting commit list rarely contains all matching commits. Only a certain number of commits are displayed and the rest are missing. "git log --boundary -S" does not return as many boundary commits as you might expect. gitk makes up for this in closevarcs() by adding missing parent (boundary) commits. However, it does not change $numcommits, which limits how many commits are shown. In the end, some commits at the end of the commit list are simply not shown. Change $numcommits whenever a missing parent is added to the current view. Signed-off-by: Stefan Dotterweich Signed-off-by: Paul Mackerras --- gitk | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gitk b/gitk index c60f180941f192..4d531b335a9a7d 100755 --- a/gitk +++ b/gitk @@ -1315,7 +1315,7 @@ proc commitonrow {row} { proc closevarcs {v} { global varctok varccommits varcid parents children - global cmitlisted commitidx vtokmod + global cmitlisted commitidx vtokmod curview numcommits set missing_parents 0 set scripts {} @@ -1340,6 +1340,9 @@ proc closevarcs {v} { } lappend varccommits($v,$b) $p incr commitidx($v) + if {$v == $curview} { + set numcommits $commitidx($v) + } set scripts [check_interest $p $scripts] } } From 75517bf2c109a9a5678bf349b4fe48fdefee3e5a Mon Sep 17 00:00:00 2001 From: Satoshi Yasushima Date: Tue, 25 Oct 2016 00:35:10 +0900 Subject: [PATCH 0048/1540] gitk: Fix Japanese translation for "marked commit" Signed-off-by: Satoshi Yasushima Signed-off-by: Paul Mackerras --- po/ja.po | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/po/ja.po b/po/ja.po index f143753db0b27c..510306b3f4c170 100644 --- a/po/ja.po +++ b/po/ja.po @@ -2,16 +2,17 @@ # Copyright (C) 2005-2015 Paul Mackerras # This file is distributed under the same license as the gitk package. # -# YOKOTA Hiroshi , 2015. # Mizar , 2009. # Junio C Hamano , 2009. +# YOKOTA Hiroshi , 2015. +# Satoshi Yasushima , 2016. msgid "" msgstr "" "Project-Id-Version: gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2015-11-12 13:00+0900\n" -"Last-Translator: YOKOTA Hiroshi \n" +"Last-Translator: Satoshi Yasushima \n" "Language-Team: Japanese\n" "Language: ja\n" "MIME-Version: 1.0\n" @@ -314,11 +315,11 @@ msgstr "マークを付けたコミットと比較する" #: gitk:2630 gitk:2641 msgid "Diff this -> marked commit" -msgstr "これと選択したコミットのdiffを見る" +msgstr "これとマークを付けたコミットのdiffを見る" #: gitk:2631 gitk:2642 msgid "Diff marked commit -> this" -msgstr "選択したコミットとこれのdiffを見る" +msgstr "マークを付けたコミットとこれのdiffを見る" #: gitk:2632 msgid "Revert this commit" From 106a6d9d85ead187f8b1be64b7a8dbf19a128743 Mon Sep 17 00:00:00 2001 From: Markus Hitter Date: Sun, 6 Nov 2016 20:38:03 +0100 Subject: [PATCH 0049/1540] gitk: Turn off undo manager in the text widget The diff text widget is read-only, so there's zero point in building an undo stack. This change reduces memory consumption of this widget by about 95%. Memory usage of the whole program for viewing a reference commit before; 579'692'744 bytes, after: 32'724'446 bytes. Test procedure: - Choose a largish commit and check it out. In this case one with 90'802 lines, 5'006'902 bytes. - Have a Tcl version with memory debugging enabled. This is, build one with --enable-symbols=mem passed to configure. - Instrument Gitk to regularly show a memory dump. E.g. by adding these code lines at the very bottom: proc memDump {} { catch { set output [memory info] puts $output } after 3000 memDump } memDump - Start Gitk, it'll load this largish commit into the diff text field automatically (because it's the current commit). - Wait until memory consumption levels out and note the numbers. Note that the numbers reported by [memory info] are much smaller than the ones reported in 'top' (1.75 GB vs. 105 MB in this case), likely due to all the instrumentation coming with the debug version of Tcl. Signed-off-by: Markus Hitter Signed-off-by: Paul Mackerras --- gitk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitk b/gitk index 4d531b335a9a7d..08ff7ceb2bddd7 100755 --- a/gitk +++ b/gitk @@ -2406,7 +2406,7 @@ proc makewindow {} { set ctext .bleft.bottom.ctext text $ctext -background $bgcolor -foreground $fgcolor \ - -state disabled -font textfont \ + -state disabled -undo 0 -font textfont \ -yscrollcommand scrolltext -wrap none \ -xscrollcommand ".bleft.bottom.sbhorizontal set" if {$have_tk85} { From 0748f41eb83194dbc310fe47b4948f611ac5b3a2 Mon Sep 17 00:00:00 2001 From: Markus Hitter Date: Mon, 7 Nov 2016 16:01:17 +0100 Subject: [PATCH 0050/1540] gitk: Remove closed file descriptors from $blobdifffd One shouldn't have descriptors of already closed files around. The first idea to deal with this (previously) ever growing array was to remove it entirely, but it's needed to detect start of a new diff with ths old diff not yet done. This happens when a user clicks on the same commit in the commit list repeatedly without delay. Signed-off-by: Markus Hitter Signed-off-by: Paul Mackerras --- gitk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gitk b/gitk index 08ff7ceb2bddd7..e037a755a513de 100755 --- a/gitk +++ b/gitk @@ -8073,7 +8073,11 @@ proc getblobdiffline {bdf ids} { $ctext conf -state normal while {[incr nr] <= 1000 && [gets $bdf line] >= 0} { if {$ids != $diffids || $bdf != $blobdifffd($ids)} { + # Older diff read. Abort it. catch {close $bdf} + if {$ids != $diffids} { + array unset blobdifffd $ids + } return 0 } parseblobdiffline $ids $line @@ -8082,6 +8086,7 @@ proc getblobdiffline {bdf ids} { blobdiffmaybeseehere [eof $bdf] if {[eof $bdf]} { catch {close $bdf} + array unset blobdifffd $ids return 0 } return [expr {$nr >= 1000? 2: 1}] From 18ae912082736b4c2c596b2c5bdcf2e032a03467 Mon Sep 17 00:00:00 2001 From: Markus Hitter Date: Mon, 7 Nov 2016 19:02:51 +0100 Subject: [PATCH 0051/1540] gitk: Clear array 'commitinfo' on reload After a reload we might have an entirely different set of commits, so keeping all of them leaks memory. Remove them all because re-creating them is not more expensive than testing wether they're still valid. Lazy (re-)creation is already well established, so a missing entry can't cause harm. Signed-off-by: Markus Hitter Signed-off-by: Paul Mackerras --- gitk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitk b/gitk index e037a755a513de..07e21d42de42d4 100755 --- a/gitk +++ b/gitk @@ -588,7 +588,7 @@ proc updatecommits {} { proc reloadcommits {} { global curview viewcomplete selectedline currentid thickerline global showneartags treediffs commitinterest cached_commitrow - global targetid + global targetid commitinfo set selid {} if {$selectedline ne {}} { @@ -609,6 +609,7 @@ proc reloadcommits {} { getallcommits } clear_display + unset -nocomplain commitinfo unset -nocomplain commitinterest unset -nocomplain cached_commitrow unset -nocomplain targetid From fbf426478e540f4737860dae622603cc0daba3d2 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Mon, 12 Dec 2016 20:46:42 +1100 Subject: [PATCH 0052/1540] gitk: Update copyright notice to 2016 Signed-off-by: Paul Mackerras --- gitk | 4 ++-- po/bg.po | 4 ++-- po/ca.po | 6 +++--- po/de.po | 4 ++-- po/es.po | 4 ++-- po/fr.po | 4 ++-- po/hu.po | 4 ++-- po/it.po | 4 ++-- po/ja.po | 4 ++-- po/pt_br.po | 4 ++-- po/pt_pt.po | 4 ++-- po/ru.po | 4 ++-- po/sv.po | 8 ++++---- po/vi.po | 4 ++-- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/gitk b/gitk index 07e21d42de42d4..a14d7a16b2dd11 100755 --- a/gitk +++ b/gitk @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright © 2005-2014 Paul Mackerras. All rights reserved. +# Copyright © 2005-2016 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. @@ -3038,7 +3038,7 @@ proc about {} { message $w.m -text [mc " Gitk - a commit viewer for git -Copyright \u00a9 2005-2014 Paul Mackerras +Copyright \u00a9 2005-2016 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg $bgcolor -relief groove diff --git a/po/bg.po b/po/bg.po index 99aa77aa63bd24..407d5550b1aba9 100644 --- a/po/bg.po +++ b/po/bg.po @@ -371,14 +371,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk — визуализация на подаванията в Git\n" "\n" -"Авторски права: © 2005-2014 Paul Mackerras\n" +"Авторски права: © 2005-2016 Paul Mackerras\n" "\n" "Използвайте и разпространявайте при условията на ОПЛ на ГНУ" diff --git a/po/ca.po b/po/ca.po index 5ad066f7ce84c4..87dfc18b4406b2 100644 --- a/po/ca.po +++ b/po/ca.po @@ -1,5 +1,5 @@ # Translation of gitk -# Copyright (C) 2005-2014 Paul Mackerras +# Copyright (C) 2005-2016 Paul Mackerras # This file is distributed under the same license as the gitk package. # Alex Henrie , 2015. # @@ -365,14 +365,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - visualitzador de comissions per al git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Useu-lo i redistribuïu-lo sota els termes de la Llicència Pública General GNU" diff --git a/po/de.po b/po/de.po index bde749ed8ac739..5db38248289baa 100644 --- a/po/de.po +++ b/po/de.po @@ -363,14 +363,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - eine Visualisierung der Git-Historie\n" "\n" -"Copyright \\u00a9 2005-2014 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public " "License" diff --git a/po/es.po b/po/es.po index ddcb0a5f68dc27..fef3bbafeead3e 100644 --- a/po/es.po +++ b/po/es.po @@ -370,14 +370,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizador de revisiones para git\n" "\n" -"Copyright \\u00a9 2005-2010 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Uso y redistribución permitidos según los términos de la Licencia Pública " "General de GNU (GNU GPL)" diff --git a/po/fr.po b/po/fr.po index c44f994fa58070..e4fac932e5b0f6 100644 --- a/po/fr.po +++ b/po/fr.po @@ -372,14 +372,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - visualisateur de commit pour git\n" "\n" -"Copyright \\u00a9 2005-2014 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Utilisation et redistribution soumises aux termes de la GNU General Public License" diff --git a/po/hu.po b/po/hu.po index 66fd75ba5b1634..79ec5a565674b0 100644 --- a/po/hu.po +++ b/po/hu.po @@ -366,14 +366,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - commit nézegető a githez\n" "\n" -"Szerzői jog \\u00a9 2005-2010 Paul Mackerras\n" +"Szerzői jog \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Használd és terjeszd a GNU General Public License feltételei mellett" diff --git a/po/it.po b/po/it.po index b5f002db7d52d3..b58d23eb2b9253 100644 --- a/po/it.po +++ b/po/it.po @@ -367,14 +367,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizzatore di revisioni per git\n" "\n" -"Copyright \\u00a9 2005-2010 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public " "License" diff --git a/po/ja.po b/po/ja.po index 510306b3f4c170..ca3c29b457bd37 100644 --- a/po/ja.po +++ b/po/ja.po @@ -374,14 +374,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - gitコミットビューア\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "使用および再配布は GNU General Public License に従ってください" diff --git a/po/pt_br.po b/po/pt_br.po index 3f78f1b7482614..1feb34854b32cc 100644 --- a/po/pt_br.po +++ b/po/pt_br.po @@ -368,14 +368,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - um visualizador de revisões para o git \n" "\n" -"Copyright ©9 2005-2010 Paul Mackerras\n" +"Copyright ©9 2005-2016 Paul Mackerras\n" "\n" "Uso e distribuição segundo os termos da Licença Pública Geral GNU" diff --git a/po/pt_pt.po b/po/pt_pt.po index a018ef91148746..f680ea86aabd08 100644 --- a/po/pt_pt.po +++ b/po/pt_pt.po @@ -371,14 +371,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - um visualizador de commits do git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use e redistribua sob os termos da GNU General Public License" diff --git a/po/ru.po b/po/ru.po index 17ed026aa7da7c..8e669e045a5022 100644 --- a/po/ru.po +++ b/po/ru.po @@ -362,10 +362,10 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright 2005-2014 Paul Mackerras\n" +"Copyright 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" -msgstr "\nGitk - программа просмотра истории репозиториев git\n\n© 2005-2014 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" +msgstr "\nGitk - программа просмотра истории репозиториев git\n\n© 2005-2016 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" #: gitk:3022 gitk:3089 gitk:9857 msgid "Close" diff --git a/po/sv.po b/po/sv.po index d9d4e87a44a5cd..32fc752db80d0f 100644 --- a/po/sv.po +++ b/po/sv.po @@ -374,14 +374,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - en incheckningsvisare för git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" @@ -1389,14 +1389,14 @@ msgstr "Felaktiga argument till gitk:" #~ "\n" #~ "Gitk - a commit viewer for git\n" #~ "\n" -#~ "Copyright © 2005-2015 Paul Mackerras\n" +#~ "Copyright © 2005-2016 Paul Mackerras\n" #~ "\n" #~ "Use and redistribute under the terms of the GNU General Public License" #~ msgstr "" #~ "\n" #~ "Gitk - en incheckningsvisare för git\n" #~ "\n" -#~ "Copyright © 2005-2015 Paul Mackerras\n" +#~ "Copyright © 2005-2016 Paul Mackerras\n" #~ "\n" #~ "Använd och vidareförmedla enligt villkoren i GNU General Public License" diff --git a/po/vi.po b/po/vi.po index 8966812368a626..59674986604891 100644 --- a/po/vi.po +++ b/po/vi.po @@ -363,14 +363,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - ứng dụng để xem các lần chuyển giao dành cho git\n" "\n" -"Bản quyền © 2005-2014 Paul Mackerras\n" +"Bản quyền © 2005-2016 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" From bf03b790471d57d1bf2a6efca6e0c640c9f37d2e Mon Sep 17 00:00:00 2001 From: "Vitaly \"_Vi\" Shukela" Date: Thu, 8 Dec 2016 04:38:14 +0300 Subject: [PATCH 0053/1540] submodule--helper: set alternateLocation for cloned submodules In 31224cbdc7 (clone: recursive and reference option triggers submodule alternates, 2016-08-17) a mechanism was added to have submodules referenced. It did not address _nested_ submodules, however. This patch makes all not just the root repository, but also all submodules (recursively) have submodule.alternateLocation and submodule.alternateErrorStrategy configured, making Git search for possible alternates for nested submodules as well. As submodule's alternate target does not end in .git/objects (rather .git/modules/qqqqqq/objects), this alternate target path restriction for in add_possible_reference_from_superproject relates from "*.git/objects" to just */objects". New tests have been added to t7408-submodule-reference. Signed-off-by: Vitaly _Vi Shukela Reviewed-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/submodule--helper.c | 19 ++++++++-- t/t7408-submodule-reference.sh | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 4beeda5f9f49d6..92fd676a2e3c5d 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -498,9 +498,9 @@ static int add_possible_reference_from_superproject( /* * If the alternate object store is another repository, try the - * standard layout with .git/modules//objects + * standard layout with .git/(modules/)+/objects */ - if (ends_with(alt->path, ".git/objects")) { + if (ends_with(alt->path, "/objects")) { char *sm_alternate; struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; @@ -583,6 +583,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) struct strbuf rel_path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; + char *sm_alternate = NULL, *error_strategy = NULL; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -672,6 +673,20 @@ static int module_clone(int argc, const char **argv, const char *prefix) die(_("could not get submodule directory for '%s'"), path); git_config_set_in_file(p, "core.worktree", relative_path(path, sm_gitdir, &rel_path)); + + /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ + git_config_get_string("submodule.alternateLocation", &sm_alternate); + if (sm_alternate) + git_config_set_in_file(p, "submodule.alternateLocation", + sm_alternate); + git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); + if (error_strategy) + git_config_set_in_file(p, "submodule.alternateErrorStrategy", + error_strategy); + + free(sm_alternate); + free(error_strategy); + strbuf_release(&sb); strbuf_release(&rel_path); free(sm_gitdir); diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index 1c1e289ffd982a..e159fc5035d08e 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -125,4 +125,70 @@ test_expect_success 'ignoring missing submodule alternates passes clone and subm ) ' +test_expect_success 'preparing second superproject with a nested submodule plus partial clone' ' + test_create_repo supersuper && + ( + cd supersuper && + echo "I am super super." >file && + git add file && + git commit -m B-super-super-initial + git submodule add "file://$base_dir/super" subwithsub && + git commit -m B-super-super-added && + git submodule update --init --recursive && + git repack -ad + ) && + git clone supersuper supersuper2 && + ( + cd supersuper2 && + git submodule update --init + ) +' + +# At this point there are three root-level positories: A, B, super and super2 + +test_expect_success 'nested submodule alternate in works and is actually used' ' + test_when_finished "rm -rf supersuper-clone" && + git clone --recursive --reference supersuper supersuper supersuper-clone && + ( + cd supersuper-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # immediate submodule has alternate: + test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub && + # nested submodule also has alternate: + test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub + ) +' + +check_that_two_of_three_alternates_are_used() { + test_alternate_is_used .git/objects/info/alternates . && + # immediate submodule has alternate: + test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub && + # but nested submodule has no alternate: + test_must_fail test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub +} + + +test_expect_success 'missing nested submodule alternate fails clone and submodule update' ' + test_when_finished "rm -rf supersuper-clone" && + test_must_fail git clone --recursive --reference supersuper2 supersuper2 supersuper-clone && + ( + cd supersuper-clone && + check_that_two_of_three_alternates_are_used && + # update of the submodule fails + test_must_fail git submodule update --init --recursive + ) +' + +test_expect_success 'missing nested submodule alternate in --reference-if-able mode' ' + test_when_finished "rm -rf supersuper-clone" && + git clone --recursive --reference-if-able supersuper2 supersuper2 supersuper-clone && + ( + cd supersuper-clone && + check_that_two_of_three_alternates_are_used && + # update of the submodule succeeds + git submodule update --init --recursive + ) +' + test_done From a0f5a0c8285395d6eb2123e0c1ce78f900e1567c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 30 Nov 2016 00:45:41 +0000 Subject: [PATCH 0054/1540] git-svn: allow "0" in SVN path components Blindly checking a path component for falsiness is unwise, as "0" is false to Perl, but a valid pathname component for SVN (or any filesystem). Found via random code reading. Signed-off-by: Eric Wong --- perl/Git/SVN/Ra.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm index e7646968011234..56ad9870bcfdfa 100644 --- a/perl/Git/SVN/Ra.pm +++ b/perl/Git/SVN/Ra.pm @@ -606,7 +606,7 @@ sub minimize_url { my $latest = $ra->get_latest_revnum; $ra->get_log("", $latest, 0, 1, 0, 1, sub {}); }; - } while ($@ && ($c = shift @components)); + } while ($@ && defined($c = shift @components)); return canonicalize_url($url); } From ea9a93dcc2136b94f66991c9630f9f098e481f49 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Dec 2016 00:06:46 +0000 Subject: [PATCH 0055/1540] git-svn: document useLogAuthor and addAuthorFrom config keys We've always supported these config keys in git-svn, so document them so users won't have to respecify them on every invocation. Reported-by: Juergen Kosel Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 5f9e65b0c4de11..9bee9b0c4c5369 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -664,13 +664,19 @@ creating the branch or tag. When retrieving svn commits into Git (as part of 'fetch', 'rebase', or 'dcommit' operations), look for the first `From:` or `Signed-off-by:` line in the log message and use that as the author string. ++ +[verse] +config key: svn.useLogAuthor + --add-author-from:: When committing to svn from Git (as part of 'commit-diff', 'set-tree' or 'dcommit' operations), if the existing log message doesn't already have a `From:` or `Signed-off-by:` line, append a `From:` line based on the Git commit's author string. If you use this, then `--use-log-author` will retrieve a valid author string for all commits. - ++ +[verse] +config key: svn.addAuthorFrom ADVANCED OPTIONS ---------------- From 1a248cf21d450eb911d01a89c84412c2da365e66 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 12 Dec 2016 11:04:33 -0800 Subject: [PATCH 0056/1540] worktree: check if a submodule uses worktrees In a later patch we want to move around the the git directory of a submodule. Both submodules as well as worktrees are involved in placing git directories at unusual places, so their functionality may collide. To react appropriately to situations where worktrees in submodules are in use, offer a new function to query the a submodule if it uses the worktree feature. An earlier approach: "Implement submodule_get_worktrees and just count them", however: This can be done cheaply (both in new code to write as well as run time) by obtaining the list of worktrees based off that submodules git directory. However as we have loaded the variables for the current repository, the values in the submodule worktree can be wrong, e.g. * core.ignorecase may differ between these two repositories * the ref resolution is broken (refs/heads/branch in the submodule resolves to the sha1 value of the `branch` in the current repository that may not exist or have another sha1) The implementation here is just checking for any files in $GIT_COMMON_DIR/worktrees for the submodule, which ought to be sufficient if the submodule is using the current repository format, which we also check. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- worktree.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ worktree.h | 5 +++++ 2 files changed, 55 insertions(+) diff --git a/worktree.c b/worktree.c index eb6121263b0d56..d4606aa8cd5ab4 100644 --- a/worktree.c +++ b/worktree.c @@ -380,3 +380,53 @@ const struct worktree *find_shared_symref(const char *symref, return existing; } + +int submodule_uses_worktrees(const char *path) +{ + char *submodule_gitdir; + struct strbuf sb = STRBUF_INIT; + DIR *dir; + struct dirent *d; + int ret; + struct repository_format format; + + submodule_gitdir = git_pathdup_submodule(path, "%s", ""); + if (!submodule_gitdir) + return 0; + + /* The env would be set for the superproject. */ + get_common_dir_noenv(&sb, submodule_gitdir); + + /* + * The check below is only known to be good for repository format + * version 0 at the time of writing this code. + */ + strbuf_addstr(&sb, "/config"); + read_repository_format(&format, sb.buf); + if (format.version != 0) { + strbuf_release(&sb); + return 1; + } + + /* Replace config by worktrees. */ + strbuf_setlen(&sb, sb.len - strlen("config")); + strbuf_addstr(&sb, "worktrees"); + + /* See if there is any file inside the worktrees directory. */ + dir = opendir(sb.buf); + strbuf_release(&sb); + free(submodule_gitdir); + + if (!dir) + return 0; + + while ((d = readdir(dir)) != NULL) { + if (is_dot_or_dotdot(d->d_name)) + continue; + + ret = 1; + break; + } + closedir(dir); + return ret; +} diff --git a/worktree.h b/worktree.h index d59ce1fee87580..6bfb985203070e 100644 --- a/worktree.h +++ b/worktree.h @@ -27,6 +27,11 @@ struct worktree { */ extern struct worktree **get_worktrees(unsigned flags); +/* + * Returns 1 if linked worktrees exist, 0 otherwise. + */ +extern int submodule_uses_worktrees(const char *path); + /* * Return git dir of the worktree. Note that the path may be relative. * If wt is NULL, git dir of current worktree is returned. From 47e83eb3b7d5410769d7f4d3930ba7fa12915680 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 12 Dec 2016 11:04:34 -0800 Subject: [PATCH 0057/1540] move connect_work_tree_and_git_dir to dir.h That function was primarily used by submodule code, but the function itself is not inherently about submodules. In the next patch we'll introduce relocate_git_dir, which can be used by worktrees as well, so find a neutral middle ground in dir.h. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- dir.c | 25 +++++++++++++++++++++++++ dir.h | 1 + submodule.c | 25 ------------------------- submodule.h | 1 - 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dir.c b/dir.c index bfa8c8a9a51297..e0efd3c2c3de4e 100644 --- a/dir.c +++ b/dir.c @@ -2748,3 +2748,28 @@ void untracked_cache_add_to_index(struct index_state *istate, { untracked_cache_invalidate_path(istate, path); } + +/* Update gitfile and core.worktree setting to connect work tree and git dir */ +void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_) +{ + struct strbuf file_name = STRBUF_INIT; + struct strbuf rel_path = STRBUF_INIT; + char *git_dir = xstrdup(real_path(git_dir_)); + char *work_tree = xstrdup(real_path(work_tree_)); + + /* Update gitfile */ + strbuf_addf(&file_name, "%s/.git", work_tree); + write_file(file_name.buf, "gitdir: %s", + relative_path(git_dir, work_tree, &rel_path)); + + /* Update core.worktree setting */ + strbuf_reset(&file_name); + strbuf_addf(&file_name, "%s/config", git_dir); + git_config_set_in_file(file_name.buf, "core.worktree", + relative_path(work_tree, git_dir, &rel_path)); + + strbuf_release(&file_name); + strbuf_release(&rel_path); + free(work_tree); + free(git_dir); +} diff --git a/dir.h b/dir.h index 97c83bb383a6b1..051674a4316905 100644 --- a/dir.h +++ b/dir.h @@ -335,4 +335,5 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked); void add_untracked_cache(struct index_state *istate); void remove_untracked_cache(struct index_state *istate); +extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); #endif diff --git a/submodule.c b/submodule.c index d4f7afe2f1ad53..0bb50b4b620a8e 100644 --- a/submodule.c +++ b/submodule.c @@ -1222,31 +1222,6 @@ int merge_submodule(unsigned char result[20], const char *path, return 0; } -/* Update gitfile and core.worktree setting to connect work tree and git dir */ -void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_) -{ - struct strbuf file_name = STRBUF_INIT; - struct strbuf rel_path = STRBUF_INIT; - char *git_dir = xstrdup(real_path(git_dir_)); - char *work_tree = xstrdup(real_path(work_tree_)); - - /* Update gitfile */ - strbuf_addf(&file_name, "%s/.git", work_tree); - write_file(file_name.buf, "gitdir: %s", - relative_path(git_dir, work_tree, &rel_path)); - - /* Update core.worktree setting */ - strbuf_reset(&file_name); - strbuf_addf(&file_name, "%s/config", git_dir); - git_config_set_in_file(file_name.buf, "core.worktree", - relative_path(work_tree, git_dir, &rel_path)); - - strbuf_release(&file_name); - strbuf_release(&rel_path); - free(work_tree); - free(git_dir); -} - int parallel_submodules(void) { return parallel_jobs; diff --git a/submodule.h b/submodule.h index d9e197a948fdab..4e3bf469b4f340 100644 --- a/submodule.h +++ b/submodule.h @@ -65,7 +65,6 @@ int merge_submodule(unsigned char result[20], const char *path, const unsigned c int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name, struct string_list *needs_pushing); int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); -void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); int parallel_submodules(void); /* From f6f858614003a3da794385cefdbddf00b85f7501 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 12 Dec 2016 11:04:35 -0800 Subject: [PATCH 0058/1540] submodule: add absorb-git-dir function When a submodule has its git dir inside the working dir, the submodule support for checkout that we plan to add in a later patch will fail. Add functionality to migrate the git directory to be absorbed into the superprojects git directory. The newly added code in this patch is structured such that other areas of Git can also make use of it. The code in the submodule--helper is a mere wrapper and option parser for the function `absorb_git_dir_into_superproject`, that takes care of embedding the submodules git directory into the superprojects git dir. That function makes use of the more abstract function for this use case `relocate_gitdir`, which can be used by e.g. the worktree code eventually to move around a git directory. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 15 +++++ builtin/submodule--helper.c | 38 +++++++++++ dir.c | 12 ++++ dir.h | 3 + git-submodule.sh | 7 +- submodule.c | 103 +++++++++++++++++++++++++++++ submodule.h | 4 ++ t/t7412-submodule-absorbgitdirs.sh | 101 ++++++++++++++++++++++++++++ 8 files changed, 282 insertions(+), 1 deletion(-) create mode 100755 t/t7412-submodule-absorbgitdirs.sh diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index d8415734753e0d..918bd1d1bd062a 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -22,6 +22,7 @@ SYNOPSIS [commit] [--] [...] 'git submodule' [--quiet] foreach [--recursive] 'git submodule' [--quiet] sync [--recursive] [--] [...] +'git submodule' [--quiet] absorbgitdirs [--] [...] DESCRIPTION @@ -245,6 +246,20 @@ sync:: If `--recursive` is specified, this command will recurse into the registered submodules, and sync any nested submodules within. +absorbgitdirs:: + If a git directory of a submodule is inside the submodule, + move the git directory of the submodule into its superprojects + `$GIT_DIR/modules` path and then connect the git directory and + its working directory by setting the `core.worktree` and adding + a .git file pointing to the git directory embedded in the + superprojects git directory. ++ +A repository that was cloned independently and later added as a submodule or +old setups have the submodules git directory inside the submodule instead of +embedded into the superprojects git directory. ++ +This command is recursive by default. + OPTIONS ------- -q:: diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 56438486672843..242d9911a6cea1 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1076,6 +1076,43 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, return 0; } +static int absorb_git_dirs(int argc, const char **argv, const char *prefix) +{ + int i; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES; + + struct option embed_gitdir_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"), + ABSORB_GITDIR_RECURSE_SUBMODULES), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper embed-git-dir [...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, embed_gitdir_options, + git_submodule_helper_usage, 0); + + gitmodules_config(); + git_config(submodule_config, NULL); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + for (i = 0; i < list.nr; i++) + absorb_git_dir_into_superproject(prefix, + list.entries[i]->name, flags); + + return 0; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -1094,6 +1131,7 @@ static struct cmd_struct commands[] = { {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"init", module_init, 0}, {"remote-branch", resolve_remote_submodule_branch, 0}, + {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/dir.c b/dir.c index e0efd3c2c3de4e..d872cc1570989b 100644 --- a/dir.c +++ b/dir.c @@ -2773,3 +2773,15 @@ void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_) free(work_tree); free(git_dir); } + +/* + * Migrate the git directory of the given path from old_git_dir to new_git_dir. + */ +void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_git_dir) +{ + if (rename(old_git_dir, new_git_dir) < 0) + die_errno(_("could not migrate git directory from '%s' to '%s'"), + old_git_dir, new_git_dir); + + connect_work_tree_and_git_dir(path, new_git_dir); +} diff --git a/dir.h b/dir.h index 051674a4316905..bf23a470af5777 100644 --- a/dir.h +++ b/dir.h @@ -336,4 +336,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra void add_untracked_cache(struct index_state *istate); void remove_untracked_cache(struct index_state *istate); extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); +extern void relocate_gitdir(const char *path, + const char *old_git_dir, + const char *new_git_dir); #endif diff --git a/git-submodule.sh b/git-submodule.sh index a024a135d6663c..9285b5c43d3658 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -1131,6 +1131,11 @@ cmd_sync() done } +cmd_absorbgitdirs() +{ + git submodule--helper absorb-git-dirs --prefix "$wt_prefix" "$@" +} + # This loop parses the command line arguments to find the # subcommand name to dispatch. Parsing of the subcommand specific # options are primarily done by the subcommand implementations. @@ -1140,7 +1145,7 @@ cmd_sync() while test $# != 0 && test -z "$command" do case "$1" in - add | foreach | init | deinit | update | status | summary | sync) + add | foreach | init | deinit | update | status | summary | sync | absorbgitdirs) command=$1 ;; -q|--quiet) diff --git a/submodule.c b/submodule.c index 0bb50b4b620a8e..45ccfb7ab4d512 100644 --- a/submodule.c +++ b/submodule.c @@ -14,6 +14,7 @@ #include "blob.h" #include "thread-utils.h" #include "quote.h" +#include "worktree.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static int parallel_jobs = 1; @@ -1237,3 +1238,105 @@ void prepare_submodule_repo_env(struct argv_array *out) } argv_array_push(out, "GIT_DIR=.git"); } + +/* + * Embeds a single submodules git directory into the superprojects git dir, + * non recursively. + */ +static void relocate_single_git_dir_into_superproject(const char *prefix, + const char *path) +{ + char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL; + const char *new_git_dir; + const struct submodule *sub; + + if (submodule_uses_worktrees(path)) + die(_("relocate_gitdir for submodule '%s' with " + "more than one worktree not supported"), path); + + old_git_dir = xstrfmt("%s/.git", path); + if (read_gitfile(old_git_dir)) + /* If it is an actual gitfile, it doesn't need migration. */ + return; + + real_old_git_dir = xstrdup(real_path(old_git_dir)); + + sub = submodule_from_path(null_sha1, path); + if (!sub) + die(_("could not lookup name for submodule '%s'"), path); + + new_git_dir = git_path("modules/%s", sub->name); + if (safe_create_leading_directories_const(new_git_dir) < 0) + die(_("could not create directory '%s'"), new_git_dir); + real_new_git_dir = xstrdup(real_path(new_git_dir)); + + if (!prefix) + prefix = get_super_prefix(); + + fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"), + prefix ? prefix : "", path, + real_old_git_dir, real_new_git_dir); + + relocate_gitdir(path, real_old_git_dir, real_new_git_dir); + + free(old_git_dir); + free(real_old_git_dir); + free(real_new_git_dir); +} + +/* + * Migrate the git directory of the submodule given by path from + * having its git directory within the working tree to the git dir nested + * in its superprojects git dir under modules/. + */ +void absorb_git_dir_into_superproject(const char *prefix, + const char *path, + unsigned flags) +{ + const char *sub_git_dir, *v; + char *real_sub_git_dir = NULL, *real_common_git_dir = NULL; + struct strbuf gitdir = STRBUF_INIT; + + strbuf_addf(&gitdir, "%s/.git", path); + sub_git_dir = resolve_gitdir(gitdir.buf); + + /* Not populated? */ + if (!sub_git_dir) + goto out; + + /* Is it already absorbed into the superprojects git dir? */ + real_sub_git_dir = xstrdup(real_path(sub_git_dir)); + real_common_git_dir = xstrdup(real_path(get_git_common_dir())); + if (!skip_prefix(real_sub_git_dir, real_common_git_dir, &v)) + relocate_single_git_dir_into_superproject(prefix, path); + + if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + + if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES) + die("BUG: we don't know how to pass the flags down?"); + + if (get_super_prefix()) + strbuf_addstr(&sb, get_super_prefix()); + strbuf_addstr(&sb, path); + strbuf_addch(&sb, '/'); + + cp.dir = path; + cp.git_cmd = 1; + cp.no_stdin = 1; + argv_array_pushl(&cp.args, "--super-prefix", sb.buf, + "submodule--helper", + "absorb-git-dirs", NULL); + prepare_submodule_repo_env(&cp.env_array); + if (run_command(&cp)) + die(_("could not recurse into submodule '%s'"), path); + + strbuf_release(&sb); + } + +out: + strbuf_release(&gitdir); + free(real_sub_git_dir); + free(real_common_git_dir); +} diff --git a/submodule.h b/submodule.h index 4e3bf469b4f340..6229054b9928d7 100644 --- a/submodule.h +++ b/submodule.h @@ -74,4 +74,8 @@ int parallel_submodules(void); */ void prepare_submodule_repo_env(struct argv_array *out); +#define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0) +extern void absorb_git_dir_into_superproject(const char *prefix, + const char *path, + unsigned flags); #endif diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh new file mode 100755 index 00000000000000..1c47780e2bf1bc --- /dev/null +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +test_description='Test submodule absorbgitdirs + +This test verifies that `git submodue absorbgitdirs` moves a submodules git +directory into the superproject. +' + +. ./test-lib.sh + +test_expect_success 'setup a real submodule' ' + git init sub1 && + test_commit -C sub1 first && + git submodule add ./sub1 && + test_tick && + git commit -m superproject +' + +test_expect_success 'absorb the git dir' ' + >expect.1 && + >expect.2 && + >actual.1 && + >actual.2 && + git status >expect.1 && + git -C sub1 rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + git fsck && + test -f sub1/.git && + test -d .git/modules/sub1 && + git status >actual.1 && + git -C sub1 rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'absorbing does not fail for deinitalized submodules' ' + test_when_finished "git submodule update --init" && + git submodule deinit --all && + git submodule absorbgitdirs && + test -d .git/modules/sub1 && + test -d sub1 && + ! test -e sub1/.git +' + +test_expect_success 'setup nested submodule' ' + git init sub1/nested && + test_commit -C sub1/nested first_nested && + git -C sub1 submodule add ./nested && + test_tick && + git -C sub1 commit -m "add nested" && + git add sub1 && + git commit -m "sub1 to include nested submodule" +' + +test_expect_success 'absorb the git dir in a nested submodule' ' + git status >expect.1 && + git -C sub1/nested rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + test -f sub1/nested/.git && + test -d .git/modules/sub1/modules/nested && + git status >actual.1 && + git -C sub1/nested rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'setup a gitlink with missing .gitmodules entry' ' + git init sub2 && + test_commit -C sub2 first && + git add sub2 && + git commit -m superproject +' + +test_expect_success 'absorbing the git dir fails for incomplete submodules' ' + git status >expect.1 && + git -C sub2 rev-parse HEAD >expect.2 && + test_must_fail git submodule absorbgitdirs && + git -C sub2 fsck && + test -d sub2/.git && + git status >actual && + git -C sub2 rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'setup a submodule with multiple worktrees' ' + # first create another unembedded git dir in a new submodule + git init sub3 && + test_commit -C sub3 first && + git submodule add ./sub3 && + test_tick && + git commit -m "add another submodule" && + git -C sub3 worktree add ../sub3_second_work_tree +' + +test_expect_success 'absorbing fails for a submodule with multiple worktrees' ' + test_must_fail git submodule absorbgitdirs sub3 2>error && + test_i18ngrep "not supported" error +' + +test_done From 05b458c104708141d2fad211d79703b3b99cc5a8 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 12 Dec 2016 10:16:52 -0800 Subject: [PATCH 0059/1540] real_path: resolve symlinks by hand The current implementation of real_path uses chdir() in order to resolve symlinks. Unfortunately this isn't thread-safe as chdir() affects a process as a whole and not just an individual thread. Instead perform the symlink resolution by hand so that the calls to chdir() can be removed, making real_path one step closer to being reentrant. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- abspath.c | 190 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 61 deletions(-) diff --git a/abspath.c b/abspath.c index 2825de85912fc7..cafcae0352928d 100644 --- a/abspath.c +++ b/abspath.c @@ -11,8 +11,45 @@ int is_directory(const char *path) return (!stat(path, &st) && S_ISDIR(st.st_mode)); } +/* removes the last path component from 'path' except if 'path' is root */ +static void strip_last_component(struct strbuf *path) +{ + size_t offset = offset_1st_component(path->buf); + size_t len = path->len; + + /* Find start of the last component */ + while (offset < len && !is_dir_sep(path->buf[len - 1])) + len--; + /* Skip sequences of multiple path-separators */ + while (offset < len && is_dir_sep(path->buf[len - 1])) + len--; + + strbuf_setlen(path, len); +} + +/* get (and remove) the next component in 'remaining' and place it in 'next' */ +static void get_next_component(struct strbuf *next, struct strbuf *remaining) +{ + char *start = NULL; + char *end = NULL; + + strbuf_reset(next); + + /* look for the next component */ + /* Skip sequences of multiple path-separators */ + for (start = remaining->buf; is_dir_sep(*start); start++) + ; /* nothing */ + /* Find end of the path component */ + for (end = start; *end && !is_dir_sep(*end); end++) + ; /* nothing */ + + strbuf_add(next, start, end - start); + /* remove the component from 'remaining' */ + strbuf_remove(remaining, 0, end - remaining->buf); +} + /* We allow "recursive" symbolic links. Only within reason, though. */ -#define MAXDEPTH 5 +#define MAXSYMLINKS 5 /* * Return the real path (i.e., absolute path, with symlinks resolved @@ -21,7 +58,6 @@ int is_directory(const char *path) * absolute_path().) The return value is a pointer to a static * buffer. * - * The input and all intermediate paths must be shorter than MAX_PATH. * The directory part of path (i.e., everything up to the last * dir_sep) must denote a valid, existing directory, but the last * component need not exist. If die_on_error is set, then die with an @@ -33,22 +69,16 @@ int is_directory(const char *path) */ static const char *real_path_internal(const char *path, int die_on_error) { - static struct strbuf sb = STRBUF_INIT; + static struct strbuf resolved = STRBUF_INIT; + struct strbuf remaining = STRBUF_INIT; + struct strbuf next = STRBUF_INIT; + struct strbuf symlink = STRBUF_INIT; char *retval = NULL; - - /* - * If we have to temporarily chdir(), store the original CWD - * here so that we can chdir() back to it at the end of the - * function: - */ - struct strbuf cwd = STRBUF_INIT; - - int depth = MAXDEPTH; - char *last_elem = NULL; + int num_symlinks = 0; struct stat st; /* We've already done it */ - if (path == sb.buf) + if (path == resolved.buf) return path; if (!*path) { @@ -58,74 +88,112 @@ static const char *real_path_internal(const char *path, int die_on_error) goto error_out; } - strbuf_reset(&sb); - strbuf_addstr(&sb, path); - - while (depth--) { - if (!is_directory(sb.buf)) { - char *last_slash = find_last_dir_sep(sb.buf); - if (last_slash) { - last_elem = xstrdup(last_slash + 1); - strbuf_setlen(&sb, last_slash - sb.buf + 1); - } else { - last_elem = xmemdupz(sb.buf, sb.len); - strbuf_reset(&sb); - } + strbuf_reset(&resolved); + + if (is_absolute_path(path)) { + /* absolute path; start with only root as being resolved */ + int offset = offset_1st_component(path); + strbuf_add(&resolved, path, offset); + strbuf_addstr(&remaining, path + offset); + } else { + /* relative path; can use CWD as the initial resolved path */ + if (strbuf_getcwd(&resolved)) { + if (die_on_error) + die_errno("unable to get current working directory"); + else + goto error_out; } + strbuf_addstr(&remaining, path); + } - if (sb.len) { - if (!cwd.len && strbuf_getcwd(&cwd)) { + /* Iterate over the remaining path components */ + while (remaining.len > 0) { + get_next_component(&next, &remaining); + + if (next.len == 0) { + continue; /* empty component */ + } else if (next.len == 1 && !strcmp(next.buf, ".")) { + continue; /* '.' component */ + } else if (next.len == 2 && !strcmp(next.buf, "..")) { + /* '..' component; strip the last path component */ + strip_last_component(&resolved); + continue; + } + + /* append the next component and resolve resultant path */ + if (!is_dir_sep(resolved.buf[resolved.len - 1])) + strbuf_addch(&resolved, '/'); + strbuf_addbuf(&resolved, &next); + + if (lstat(resolved.buf, &st)) { + /* error out unless this was the last component */ + if (errno != ENOENT || remaining.len) { if (die_on_error) - die_errno("Could not get current working directory"); + die_errno("Invalid path '%s'", + resolved.buf); else goto error_out; } + } else if (S_ISLNK(st.st_mode)) { + ssize_t len; + strbuf_reset(&symlink); - if (chdir(sb.buf)) { + if (num_symlinks++ > MAXSYMLINKS) { if (die_on_error) - die_errno("Could not switch to '%s'", - sb.buf); + die("More than %d nested symlinks " + "on path '%s'", MAXSYMLINKS, path); else goto error_out; } - } - if (strbuf_getcwd(&sb)) { - if (die_on_error) - die_errno("Could not get current working directory"); - else - goto error_out; - } - - if (last_elem) { - if (sb.len && !is_dir_sep(sb.buf[sb.len - 1])) - strbuf_addch(&sb, '/'); - strbuf_addstr(&sb, last_elem); - free(last_elem); - last_elem = NULL; - } - if (!lstat(sb.buf, &st) && S_ISLNK(st.st_mode)) { - struct strbuf next_sb = STRBUF_INIT; - ssize_t len = strbuf_readlink(&next_sb, sb.buf, 0); + len = strbuf_readlink(&symlink, resolved.buf, + st.st_size); if (len < 0) { if (die_on_error) die_errno("Invalid symlink '%s'", - sb.buf); + resolved.buf); else goto error_out; } - strbuf_swap(&sb, &next_sb); - strbuf_release(&next_sb); - } else - break; + + if (is_absolute_path(symlink.buf)) { + /* absolute symlink; set resolved to root */ + int offset = offset_1st_component(symlink.buf); + strbuf_reset(&resolved); + strbuf_add(&resolved, symlink.buf, offset); + strbuf_remove(&symlink, 0, offset); + } else { + /* + * relative symlink + * strip off the last component since it will + * be replaced with the contents of the symlink + */ + strip_last_component(&resolved); + } + + /* + * if there are still remaining components to resolve + * then append them to symlink + */ + if (remaining.len) { + strbuf_addch(&symlink, '/'); + strbuf_addbuf(&symlink, &remaining); + } + + /* + * use the symlink as the remaining components that + * need to be resloved + */ + strbuf_swap(&symlink, &remaining); + } } - retval = sb.buf; + retval = resolved.buf; + error_out: - free(last_elem); - if (cwd.len && chdir(cwd.buf)) - die_errno("Could not change back to '%s'", cwd.buf); - strbuf_release(&cwd); + strbuf_release(&remaining); + strbuf_release(&next); + strbuf_release(&symlink); return retval; } From a1ae48410dce23c1e81e76aabaeb4eb01b065763 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 12 Dec 2016 10:16:53 -0800 Subject: [PATCH 0060/1540] real_path: convert real_path_internal to strbuf_realpath Change the name of real_path_internal to strbuf_realpath. In addition push the static strbuf up to its callers and instead take as a parameter a pointer to a strbuf to use for the final result. This change makes strbuf_realpath reentrant. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- abspath.c | 53 +++++++++++++++++++++++++---------------------------- cache.h | 2 ++ 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/abspath.c b/abspath.c index cafcae0352928d..8c6c76b05e397c 100644 --- a/abspath.c +++ b/abspath.c @@ -55,21 +55,17 @@ static void get_next_component(struct strbuf *next, struct strbuf *remaining) * Return the real path (i.e., absolute path, with symlinks resolved * and extra slashes removed) equivalent to the specified path. (If * you want an absolute path but don't mind links, use - * absolute_path().) The return value is a pointer to a static - * buffer. + * absolute_path().) Places the resolved realpath in the provided strbuf. * * The directory part of path (i.e., everything up to the last * dir_sep) must denote a valid, existing directory, but the last * component need not exist. If die_on_error is set, then die with an * informative error message if there is a problem. Otherwise, return * NULL on errors (without generating any output). - * - * If path is our buffer, then return path, as it's already what the - * user wants. */ -static const char *real_path_internal(const char *path, int die_on_error) +char *strbuf_realpath(struct strbuf *resolved, const char *path, + int die_on_error) { - static struct strbuf resolved = STRBUF_INIT; struct strbuf remaining = STRBUF_INIT; struct strbuf next = STRBUF_INIT; struct strbuf symlink = STRBUF_INIT; @@ -77,10 +73,6 @@ static const char *real_path_internal(const char *path, int die_on_error) int num_symlinks = 0; struct stat st; - /* We've already done it */ - if (path == resolved.buf) - return path; - if (!*path) { if (die_on_error) die("The empty string is not a valid path"); @@ -88,16 +80,16 @@ static const char *real_path_internal(const char *path, int die_on_error) goto error_out; } - strbuf_reset(&resolved); + strbuf_reset(resolved); if (is_absolute_path(path)) { /* absolute path; start with only root as being resolved */ int offset = offset_1st_component(path); - strbuf_add(&resolved, path, offset); + strbuf_add(resolved, path, offset); strbuf_addstr(&remaining, path + offset); } else { /* relative path; can use CWD as the initial resolved path */ - if (strbuf_getcwd(&resolved)) { + if (strbuf_getcwd(resolved)) { if (die_on_error) die_errno("unable to get current working directory"); else @@ -116,21 +108,21 @@ static const char *real_path_internal(const char *path, int die_on_error) continue; /* '.' component */ } else if (next.len == 2 && !strcmp(next.buf, "..")) { /* '..' component; strip the last path component */ - strip_last_component(&resolved); + strip_last_component(resolved); continue; } /* append the next component and resolve resultant path */ - if (!is_dir_sep(resolved.buf[resolved.len - 1])) - strbuf_addch(&resolved, '/'); - strbuf_addbuf(&resolved, &next); + if (!is_dir_sep(resolved->buf[resolved->len - 1])) + strbuf_addch(resolved, '/'); + strbuf_addbuf(resolved, &next); - if (lstat(resolved.buf, &st)) { + if (lstat(resolved->buf, &st)) { /* error out unless this was the last component */ if (errno != ENOENT || remaining.len) { if (die_on_error) die_errno("Invalid path '%s'", - resolved.buf); + resolved->buf); else goto error_out; } @@ -146,12 +138,12 @@ static const char *real_path_internal(const char *path, int die_on_error) goto error_out; } - len = strbuf_readlink(&symlink, resolved.buf, + len = strbuf_readlink(&symlink, resolved->buf, st.st_size); if (len < 0) { if (die_on_error) die_errno("Invalid symlink '%s'", - resolved.buf); + resolved->buf); else goto error_out; } @@ -159,8 +151,8 @@ static const char *real_path_internal(const char *path, int die_on_error) if (is_absolute_path(symlink.buf)) { /* absolute symlink; set resolved to root */ int offset = offset_1st_component(symlink.buf); - strbuf_reset(&resolved); - strbuf_add(&resolved, symlink.buf, offset); + strbuf_reset(resolved); + strbuf_add(resolved, symlink.buf, offset); strbuf_remove(&symlink, 0, offset); } else { /* @@ -168,7 +160,7 @@ static const char *real_path_internal(const char *path, int die_on_error) * strip off the last component since it will * be replaced with the contents of the symlink */ - strip_last_component(&resolved); + strip_last_component(resolved); } /* @@ -188,24 +180,29 @@ static const char *real_path_internal(const char *path, int die_on_error) } } - retval = resolved.buf; + retval = resolved->buf; error_out: strbuf_release(&remaining); strbuf_release(&next); strbuf_release(&symlink); + if (!retval) + strbuf_reset(resolved); + return retval; } const char *real_path(const char *path) { - return real_path_internal(path, 1); + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 1); } const char *real_path_if_valid(const char *path) { - return real_path_internal(path, 0); + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 0); } /* diff --git a/cache.h b/cache.h index a50a61a19787de..7a81294036965b 100644 --- a/cache.h +++ b/cache.h @@ -1064,6 +1064,8 @@ static inline int is_absolute_path(const char *path) return is_dir_sep(path[0]) || has_dos_drive_prefix(path); } int is_directory(const char *); +char *strbuf_realpath(struct strbuf *resolved, const char *path, + int die_on_error); const char *real_path(const char *path); const char *real_path_if_valid(const char *path); const char *absolute_path(const char *path); From 72417640769c91408d15cdbab3160bc494f49c7f Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 12 Dec 2016 10:16:54 -0800 Subject: [PATCH 0061/1540] real_path: create real_pathdup Create real_pathdup which returns a caller owned string of the resolved realpath based on the provide path. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- abspath.c | 13 +++++++++++++ cache.h | 1 + 2 files changed, 14 insertions(+) diff --git a/abspath.c b/abspath.c index 8c6c76b05e397c..79ee3108678c97 100644 --- a/abspath.c +++ b/abspath.c @@ -205,6 +205,19 @@ const char *real_path_if_valid(const char *path) return strbuf_realpath(&realpath, path, 0); } +char *real_pathdup(const char *path) +{ + struct strbuf realpath = STRBUF_INIT; + char *retval = NULL; + + if (strbuf_realpath(&realpath, path, 0)) + retval = strbuf_detach(&realpath, NULL); + + strbuf_release(&realpath); + + return retval; +} + /* * Use this to get an absolute path from a relative one. If you want * to resolve links, you should use real_path. diff --git a/cache.h b/cache.h index 7a81294036965b..e12a5d9129b938 100644 --- a/cache.h +++ b/cache.h @@ -1068,6 +1068,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path, int die_on_error); const char *real_path(const char *path); const char *real_path_if_valid(const char *path); +char *real_pathdup(const char *path); const char *absolute_path(const char *path); const char *remove_leading_path(const char *in, const char *prefix); const char *relative_path(const char *in, const char *prefix, struct strbuf *sb); From 4ac9006f832d98ca1f25d956e12f3ff79e0d25bc Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 12 Dec 2016 10:16:55 -0800 Subject: [PATCH 0062/1540] real_path: have callers use real_pathdup and strbuf_realpath Migrate callers of real_path() who duplicate the retern value to use real_pathdup or strbuf_realpath. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- builtin/init-db.c | 6 +++--- environment.c | 2 +- setup.c | 13 ++++++++----- sha1_file.c | 2 +- submodule.c | 2 +- transport.c | 2 +- worktree.c | 2 +- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/builtin/init-db.c b/builtin/init-db.c index 2399b97d902668..76d68fad001083 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -338,7 +338,7 @@ int init_db(const char *git_dir, const char *real_git_dir, { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; - char *original_git_dir = xstrdup(real_path(git_dir)); + char *original_git_dir = real_pathdup(git_dir); if (real_git_dir) { struct stat st; @@ -489,7 +489,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); if (real_git_dir && !is_absolute_path(real_git_dir)) - real_git_dir = xstrdup(real_path(real_git_dir)); + real_git_dir = real_pathdup(real_git_dir); if (argc == 1) { int mkdir_tried = 0; @@ -560,7 +560,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *git_dir_parent = strrchr(git_dir, '/'); if (git_dir_parent) { char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(real_path(rel)); + git_work_tree_cfg = real_pathdup(rel); free(rel); } if (!git_work_tree_cfg) diff --git a/environment.c b/environment.c index 0935ec696e530e..9b943d2d5bbc10 100644 --- a/environment.c +++ b/environment.c @@ -259,7 +259,7 @@ void set_git_work_tree(const char *new_work_tree) return; } git_work_tree_initialized = 1; - work_tree = xstrdup(real_path(new_work_tree)); + work_tree = real_pathdup(new_work_tree); } const char *get_git_work_tree(void) diff --git a/setup.c b/setup.c index fe572b82c35539..1b534a750810f3 100644 --- a/setup.c +++ b/setup.c @@ -256,8 +256,10 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir) strbuf_addbuf(&path, &data); strbuf_addstr(sb, real_path(path.buf)); ret = 1; - } else + } else { strbuf_addstr(sb, gitdir); + } + strbuf_release(&data); strbuf_release(&path); return ret; @@ -692,7 +694,7 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { if (offset != cwd->len && !is_absolute_path(gitdir)) - gitdir = xstrdup(real_path(gitdir)); + gitdir = real_pathdup(gitdir); if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); return setup_explicit_git_dir(gitdir, cwd, nongit_ok); @@ -800,11 +802,12 @@ static int canonicalize_ceiling_entry(struct string_list_item *item, /* Keep entry but do not canonicalize it */ return 1; } else { - const char *real_path = real_path_if_valid(ceil); - if (!real_path) + char *real_path = real_pathdup(ceil); + if (!real_path) { return 0; + } free(item->string); - item->string = xstrdup(real_path); + item->string = real_path; return 1; } } diff --git a/sha1_file.c b/sha1_file.c index 9c86d1924a23e4..6a03cc39342cde 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -291,7 +291,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base, struct strbuf pathbuf = STRBUF_INIT; if (!is_absolute_path(entry) && relative_base) { - strbuf_addstr(&pathbuf, real_path(relative_base)); + strbuf_realpath(&pathbuf, relative_base, 1); strbuf_addch(&pathbuf, '/'); } strbuf_addstr(&pathbuf, entry); diff --git a/submodule.c b/submodule.c index 6f7d883de950af..c85ba50110606a 100644 --- a/submodule.c +++ b/submodule.c @@ -1227,7 +1227,7 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) { struct strbuf file_name = STRBUF_INIT; struct strbuf rel_path = STRBUF_INIT; - const char *real_work_tree = xstrdup(real_path(work_tree)); + const char *real_work_tree = real_pathdup(work_tree); /* Update gitfile */ strbuf_addf(&file_name, "%s/.git", work_tree); diff --git a/transport.c b/transport.c index d57e8dec28d689..236c6f6b095ad5 100644 --- a/transport.c +++ b/transport.c @@ -1130,7 +1130,7 @@ static int refs_from_alternate_cb(struct alternate_object_database *e, const struct ref *extra; struct alternate_refs_data *cb = data; - other = xstrdup(real_path(e->path)); + other = real_pathdup(e->path); len = strlen(other); while (other[len-1] == '/') diff --git a/worktree.c b/worktree.c index f7869f8d6072c9..c90e0139855bd3 100644 --- a/worktree.c +++ b/worktree.c @@ -255,7 +255,7 @@ struct worktree *find_worktree(struct worktree **list, return wt; arg = prefix_filename(prefix, strlen(prefix), arg); - path = xstrdup(real_path(arg)); + path = real_pathdup(arg); for (; *list; list++) if (!fspathcmp(path, real_path((*list)->path))) break; From de2efebf7ce2b308ea77d8b06f971e935238cd2f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 13 Dec 2016 14:13:17 -0800 Subject: [PATCH 0063/1540] Early fixes for 2.11.x series Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index 08152d64b93917..2d68e987cc202d 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -40,4 +40,14 @@ Unless otherwise noted, all the fixes since v2.9 in the maintenance track are contained in this release (see the maintenance releases' notes for details). + * We often decide if a session is interactive by checking if the + standard I/O streams are connected to a TTY, but isatty() that + comes with Windows incorrectly returned true if it is used on NUL + (i.e. an equivalent to /dev/null). This has been fixed. + (merge cbb3f3c9b1 js/mingw-isatty later to maint). + + * "git svn" did not work well with path components that are "0", and + some configuration variable it uses were not documented. + (merge ea9a93dcc2 ew/svn-fixes later to maint). + * Other minor doc, test and build updates and code cleanups. From 378f7be1e74661ff1480cc44a5f039ef85da7288 Mon Sep 17 00:00:00 2001 From: Luke Diamand Date: Tue, 13 Dec 2016 21:51:28 +0000 Subject: [PATCH 0064/1540] git-p4: support git worktrees git-p4 would attempt to find the git directory using its own specific code, which did not know about git worktrees. Rework it to use "git rev-parse --git-dir" instead. Add test cases for worktree usage and specifying git directory via --git-dir and $GIT_DIR. Signed-off-by: Luke Diamand Signed-off-by: Junio C Hamano --- git-p4.py | 17 +++++++++++++---- t/t9800-git-p4-basic.sh | 20 ++++++++++++++++++++ t/t9806-git-p4-options.sh | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/git-p4.py b/git-p4.py index fd5ca524626c40..6a1f65f4efe6ba 100755 --- a/git-p4.py +++ b/git-p4.py @@ -85,6 +85,16 @@ def p4_build_cmd(cmd): real_cmd += cmd return real_cmd +def git_dir(path): + """ Return TRUE if the given path is a git directory (/path/to/dir/.git). + This won't automatically add ".git" to a directory. + """ + d = read_pipe(["git", "--git-dir", path, "rev-parse", "--git-dir"], True).strip() + if not d or len(d) == 0: + return None + else: + return d + def chdir(path, is_client_path=False): """Do chdir to the given path, and set the PWD environment variable for use by P4. It does not look at getcwd() output. @@ -563,10 +573,7 @@ def currentGitBranch(): return read_pipe(["git", "name-rev", "HEAD"]).split(" ")[1].strip() def isValidGitDir(path): - if (os.path.exists(path + "/HEAD") - and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")): - return True; - return False + return git_dir(path) != None def parseRevision(ref): return read_pipe("git rev-parse %s" % ref).strip() @@ -3682,6 +3689,7 @@ def main(): if cmd.gitdir == None: cmd.gitdir = os.path.abspath(".git") if not isValidGitDir(cmd.gitdir): + # "rev-parse --git-dir" without arguments will try $PWD/.git cmd.gitdir = read_pipe("git rev-parse --git-dir").strip() if os.path.exists(cmd.gitdir): cdup = read_pipe("git rev-parse --show-cdup").strip() @@ -3694,6 +3702,7 @@ def main(): else: die("fatal: cannot locate git repository at %s" % cmd.gitdir) + # so git commands invoked from the P4 workspace will succeed os.environ["GIT_DIR"] = cmd.gitdir if not cmd.run(args): diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 0730f18d0f83f4..093e9bd586da55 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -257,6 +257,26 @@ test_expect_success 'submit from detached head' ' ) ' +test_expect_success 'submit from worktree' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git worktree add ../worktree-test + ) && + ( + cd "$git/../worktree-test" && + test_commit "worktree-commit" && + git config git-p4.skipSubmitEdit true && + git p4 submit + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file worktree-commit.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh index 254d428b73fe49..1ab76c4246f215 100755 --- a/t/t9806-git-p4-options.sh +++ b/t/t9806-git-p4-options.sh @@ -269,6 +269,38 @@ test_expect_success 'submit works with two branches' ' ) ' +test_expect_success 'use --git-dir option and GIT_DIR' ' + test_when_finished cleanup_git && + git p4 clone //depot --destination="$git" && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit first-change && + git p4 submit --git-dir "$git" + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file first-change.t && + echo "cli_file" >cli_file.t && + p4 add cli_file.t && + p4 submit -d "cli change" + ) && + (git --git-dir "$git" p4 sync) && + (cd "$git" && git checkout -q p4/master) && + test_path_is_file "$git"/cli_file.t && + ( + cd "$cli" && + echo "cli_file2" >cli_file2.t && + p4 add cli_file2.t && + p4 submit -d "cli change2" + ) && + (GIT_DIR="$git" git p4 sync) && + (cd "$git" && git checkout -q p4/master) && + test_path_is_file "$git"/cli_file2.t +' + + test_expect_success 'kill p4d' ' kill_p4d ' From e951ebca91d914e3fb579011c9218f59c67cf2fd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 14 Dec 2016 09:26:55 -0500 Subject: [PATCH 0065/1540] Makefile: reformat FIND_SOURCE_FILES As we add to this in future commits, the formatting is going to make it harder and harder to read. Let's write it more as we would in a shell script, putting each logical block on its own line. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f53fcc90d71b7e..f42b1953d8d66f 100644 --- a/Makefile +++ b/Makefile @@ -2149,9 +2149,12 @@ endif po/build/locale/%/LC_MESSAGES/git.mo: po/%.po $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $< -FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \ - $(FIND) . \( -name .git -type d -prune \) \ - -o \( -name '*.[hcS]' -type f -print \) ) +FIND_SOURCE_FILES = ( \ + git ls-files '*.[hcS]' 2>/dev/null || \ + $(FIND) . \ + \( -name .git -type d -prune \) \ + -o \( -name '*.[hcS]' -type f -print \) \ + ) $(ETAGS_TARGET): FORCE $(RM) $(ETAGS_TARGET) From e6fc85b11f402ababa4b46ec1121a662ce1fce8e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 14 Dec 2016 09:28:04 -0500 Subject: [PATCH 0066/1540] Makefile: exclude test cruft from FIND_SOURCE_FILES The test directory may contain three types of files that match our patterns: 1. Helper programs in t/helper. 2. Sample data files (e.g., t/t4051/hello.c). 3. Untracked cruft in trash directories and t/perf/build. We want to match (1), but not the other two, as they just clutter up the list. For the ls-files method, we can drop (2) with a negative pathspec. We do not have to care about (3), since ls-files will not list untracked files. For `find`, we can match both cases with `-prune` patterns. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f42b1953d8d66f..001126931d0b77 100644 --- a/Makefile +++ b/Makefile @@ -2150,9 +2150,15 @@ po/build/locale/%/LC_MESSAGES/git.mo: po/%.po $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $< FIND_SOURCE_FILES = ( \ - git ls-files '*.[hcS]' 2>/dev/null || \ + git ls-files \ + '*.[hcS]' \ + ':!*[tp][0-9][0-9][0-9][0-9]*' \ + 2>/dev/null || \ $(FIND) . \ \( -name .git -type d -prune \) \ + -o \( -name '[tp][0-9][0-9][0-9][0-9]' -type d -prune \) \ + -o \( -name build -type d -prune \) \ + -o \( -name 'trash*' -type d -prune \) \ -o \( -name '*.[hcS]' -type f -print \) \ ) From 8fa2043293831402b84c3446efa62640c01c8121 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 14 Dec 2016 09:29:44 -0500 Subject: [PATCH 0067/1540] Makefile: match shell scripts in FIND_SOURCE_FILES We feed FIND_SOURCE_FILES to ctags to help developers navigate to particular functions, but we only feed C source code. The same feature can be helpful when working with shell scripts (especially the test suite). Modern versions of ctags know how to parse shell scripts; we just need to feed the filenames to it. This patch specifically avoids including the individual test scripts themselves. Those are unlikely to be of interest, and there are a lot of them to process. It does pick up test-lib.sh and test-lib-functions.sh. Note that our negative pathspec already excludes the individual scripts for the ls-files case, but we need to loosen the `find` rule to match it. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 001126931d0b77..ef8de4a754fced 100644 --- a/Makefile +++ b/Makefile @@ -2152,14 +2152,16 @@ po/build/locale/%/LC_MESSAGES/git.mo: po/%.po FIND_SOURCE_FILES = ( \ git ls-files \ '*.[hcS]' \ + '*.sh' \ ':!*[tp][0-9][0-9][0-9][0-9]*' \ 2>/dev/null || \ $(FIND) . \ \( -name .git -type d -prune \) \ - -o \( -name '[tp][0-9][0-9][0-9][0-9]' -type d -prune \) \ + -o \( -name '[tp][0-9][0-9][0-9][0-9]*' -prune \) \ -o \( -name build -type d -prune \) \ -o \( -name 'trash*' -type d -prune \) \ -o \( -name '*.[hcS]' -type f -print \) \ + -o \( -name '*.sh' -type f -print \) \ ) $(ETAGS_TARGET): FORCE From 046e4c1c09aa4fa9865b1fd755aa99d8617465d2 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 14 Dec 2016 09:32:35 -0500 Subject: [PATCH 0068/1540] Makefile: exclude contrib from FIND_SOURCE_FILES When you're working on the git project, you're unlikely to care about random bits in contrib/ (e.g., you would not want to jump to the copy of xmalloc in the wincred credential helper). Nobody has really complained because there are relatively few C files in contrib. Now that we're matching shell scripts, too, we get quite a few more hits, especially in the obsolete contrib/examples directory. Looking for usage() should turn up the one in git-sh-setup, not in some long-dead version of git-clone. Let's just exclude all of contrib. Any specific projects there which are big enough to want tags can generate them separately. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index ef8de4a754fced..76267262c155fa 100644 --- a/Makefile +++ b/Makefile @@ -2154,10 +2154,12 @@ FIND_SOURCE_FILES = ( \ '*.[hcS]' \ '*.sh' \ ':!*[tp][0-9][0-9][0-9][0-9]*' \ + ':!contrib' \ 2>/dev/null || \ $(FIND) . \ \( -name .git -type d -prune \) \ -o \( -name '[tp][0-9][0-9][0-9][0-9]*' -prune \) \ + -o \( -name contrib -type d -prune \) \ -o \( -name build -type d -prune \) \ -o \( -name 'trash*' -type d -prune \) \ -o \( -name '*.[hcS]' -type f -print \) \ From 367ff694281ce569edd8f6e444fc770f92f5d215 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 14 Dec 2016 21:37:55 +1300 Subject: [PATCH 0069/1540] merge: add '--continue' option as a synonym for 'git commit' Teach 'git merge' the --continue option which allows 'continuing' a merge by completing it. The traditional way of completing a merge after resolving conflicts is to use 'git commit'. Now with commands like 'git rebase' and 'git cherry-pick' having a '--continue' option adding such an option to 'git merge' presents a consistent UI. Signed-off-by: Chris Packham Signed-off-by: Junio C Hamano --- Documentation/git-merge.txt | 8 ++++++++ builtin/merge.c | 21 +++++++++++++++++++++ t/t7600-merge.sh | 9 +++++++++ 3 files changed, 38 insertions(+) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index b758d5556caaee..ca3c27b88a4ea0 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -15,6 +15,7 @@ SYNOPSIS [--[no-]rerere-autoupdate] [-m ] [...] 'git merge' HEAD ... 'git merge' --abort +'git merge' --continue DESCRIPTION ----------- @@ -61,6 +62,8 @@ reconstruct the original (pre-merge) changes. Therefore: discouraged: while possible, it may leave you in a state that is hard to back out of in the case of a conflict. +The fourth syntax ("`git merge --continue`") can only be run after the +merge has resulted in conflicts. OPTIONS ------- @@ -99,6 +102,11 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--continue:: + After a 'git merge' stops due to conflicts you can conclude the + merge by running 'git merge --continue' (see "HOW TO RESOLVE + CONFLICTS" section below). + ...:: Commits, usually other branch heads, to merge into our branch. Specifying more than one commit will create a merge with diff --git a/builtin/merge.c b/builtin/merge.c index b65eeaa87d303b..836ec281b497e7 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = { N_("git merge [] [...]"), N_("git merge [] HEAD "), N_("git merge --abort"), + N_("git merge --continue"), NULL }; @@ -65,6 +66,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; @@ -223,6 +225,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "continue", &continue_current_merge, + N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), @@ -1125,6 +1129,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list *remoteheads, *p; void *branch_to_free; + int orig_argc = argc; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1166,6 +1171,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (continue_current_merge) { + int nargc = 1; + const char *nargv[] = {"commit", NULL}; + + if (orig_argc != 2) + usage_msg_opt("--continue expects no arguments", + builtin_merge_usage, builtin_merge_options); + + if (!file_exists(git_path_merge_head())) + die(_("There is no merge in progress (MERGE_HEAD missing).")); + + /* Invoke 'git commit' */ + ret = cmd_commit(nargc, nargv, prefix); + goto done; + } + if (read_cache_unmerged()) die_resolve_conflict("merge"); diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 85248a14b61d68..682139c4ea30c7 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -154,6 +154,8 @@ test_expect_success 'test option parsing' ' test_must_fail git merge -s foobar c1 && test_must_fail git merge -s=foobar c1 && test_must_fail git merge -m && + test_must_fail git merge --continue foobar && + test_must_fail git merge --continue --quiet && test_must_fail git merge ' @@ -763,4 +765,11 @@ test_expect_success 'merge nothing into void' ' ) ' +test_expect_success 'merge can be completed with --continue' ' + git reset --hard c0 && + git merge --no-ff --no-commit c1 && + git merge --continue && + verify_parents $c0 $c1 +' + test_done From c261a87e704679203510d48ff4db7bc7006b8a5a Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 14 Dec 2016 21:37:56 +1300 Subject: [PATCH 0070/1540] completion: add --continue option for merge Add 'git merge --continue' option when completing. Signed-off-by: Chris Packham Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 21016bf8dfe875..1f97ffae199232 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1552,7 +1552,7 @@ _git_merge () case "$cur" in --*) __gitcomp "$__git_merge_options - --rerere-autoupdate --no-rerere-autoupdate --abort" + --rerere-autoupdate --no-rerere-autoupdate --abort --continue" return esac __gitcomp_nl "$(__git_refs)" From 042e290da6aee0a9f21cd5890ccf231c266e177e Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 14 Dec 2016 21:37:57 +1300 Subject: [PATCH 0071/1540] merge: ensure '--abort' option takes no arguments Like '--continue', the '--abort' option doesn't make any sense with other options or arguments to 'git merge' so ensure that none are present. Signed-off-by: Chris Packham Signed-off-by: Junio C Hamano --- builtin/merge.c | 4 ++++ t/t7600-merge.sh | 2 ++ 2 files changed, 6 insertions(+) diff --git a/builtin/merge.c b/builtin/merge.c index 836ec281b497e7..668aaffb849d5c 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1163,6 +1163,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + if (orig_argc != 2) + usage_msg_opt("--abort expects no arguments", + builtin_merge_usage, builtin_merge_options); + if (!file_exists(git_path_merge_head())) die(_("There is no merge to abort (MERGE_HEAD missing).")); diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 682139c4ea30c7..2ebda509ac337f 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -154,6 +154,8 @@ test_expect_success 'test option parsing' ' test_must_fail git merge -s foobar c1 && test_must_fail git merge -s=foobar c1 && test_must_fail git merge -m && + test_must_fail git merge --abort foobar && + test_must_fail git merge --abort --quiet && test_must_fail git merge --continue foobar && test_must_fail git merge --continue --quiet && test_must_fail git merge From 2db87101fc3b221b1c015e763d80ee82681d8753 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:24 -0100 Subject: [PATCH 0072/1540] Git.pm: add subroutines for commenting lines Add subroutines prefix_lines and comment_lines. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- perl/Git.pm | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/perl/Git.pm b/perl/Git.pm index ce7e4e8da3947b..f699fee9a17282 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -1421,6 +1421,44 @@ sub END { } # %TEMP_* Lexical Context +=item prefix_lines ( PREFIX, STRING [, STRING... ]) + +Prefixes lines in C with C. + +=cut + +sub prefix_lines { + my $prefix = shift; + my $string = join("\n", @_); + $string =~ s/^/$prefix/mg; + return $string; +} + +=item get_comment_line_char ( ) + +Gets the core.commentchar configuration value. +The value falls-back to '#' if core.commentchar is set to 'auto'. + +=cut + +sub get_comment_line_char { + my $comment_line_char = config("core.commentchar") || '#'; + $comment_line_char = '#' if ($comment_line_char eq 'auto'); + $comment_line_char = '#' if (length($comment_line_char) != 1); + return $comment_line_char; +} + +=item comment_lines ( STRING [, STRING... ]) + +Comments lines following core.commentchar configuration. + +=cut + +sub comment_lines { + my $comment_line_char = get_comment_line_char; + return prefix_lines("$comment_line_char ", @_); +} + =back =head1 ERROR HANDLING From 258e7790b3a57c3b1e8c1fdc3e2d1697b9e0b184 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:25 -0100 Subject: [PATCH 0073/1540] i18n: add--interactive: mark strings for translation Mark simple strings (without interpolation) for translation. Brackets around first parameter of ternary operator is necessary because otherwise xgettext fails to extract strings marked for translation from the rest of the file. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 76 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 642cce1ac6e158..a4b29840a9806f 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -4,6 +4,7 @@ use strict; use warnings; use Git; +use Git::I18N; binmode(STDOUT, ":raw"); @@ -252,8 +253,9 @@ sub list_untracked { run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } -my $status_fmt = '%12s %12s %s'; -my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); +# TRANSLATORS: you can adjust this to align "git add -i" status menu +my $status_fmt = __('%12s %12s %s'); +my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path')); { my $initial; @@ -679,7 +681,7 @@ sub update_cmd { my @mods = list_modified('file-only'); return if (!@mods); - my @update = list_and_choose({ PROMPT => 'Update', + my @update = list_and_choose({ PROMPT => __('Update'), HEADER => $status_head, }, @mods); if (@update) { @@ -691,7 +693,7 @@ sub update_cmd { } sub revert_cmd { - my @update = list_and_choose({ PROMPT => 'Revert', + my @update = list_and_choose({ PROMPT => __('Revert'), HEADER => $status_head, }, list_modified()); if (@update) { @@ -725,13 +727,13 @@ sub revert_cmd { } sub add_untracked_cmd { - my @add = list_and_choose({ PROMPT => 'Add untracked' }, + my @add = list_and_choose({ PROMPT => __('Add untracked') }, list_untracked()); if (@add) { system(qw(git update-index --add --), @add); say_n_paths('added', @add); } else { - print "No untracked files.\n"; + print __("No untracked files.\n"); } print "\n"; } @@ -1163,8 +1165,14 @@ sub edit_hunk_loop { } else { prompt_yesno( - 'Your edited hunk does not apply. Edit again ' - . '(saying "no" discards!) [y/n]? ' + # TRANSLATORS: do not translate [y/n] + # The program will only accept that input + # at this point. + # Consider translating (saying "no" discards!) as + # (saying "n" for "no" discards!) if the translation + # of the word "no" does not start with n. + __('Your edited hunk does not apply. Edit again ' + . '(saying "no" discards!) [y/n]? ') ) or return undef; } } @@ -1210,11 +1218,11 @@ sub apply_patch_for_checkout_commit { run_git_apply 'apply '.$reverse, @_; return 1; } elsif (!$applies_index) { - print colored $error_color, "The selected hunks do not apply to the index!\n"; - if (prompt_yesno "Apply them to the worktree anyway? ") { + print colored $error_color, __("The selected hunks do not apply to the index!\n"); + if (prompt_yesno __("Apply them to the worktree anyway? ")) { return run_git_apply 'apply '.$reverse, @_; } else { - print colored $error_color, "Nothing was applied.\n"; + print colored $error_color, __("Nothing was applied.\n"); return 0; } } else { @@ -1234,9 +1242,9 @@ sub patch_update_cmd { if (!@mods) { if (@all_mods) { - print STDERR "Only binary files changed.\n"; + print STDERR __("Only binary files changed.\n"); } else { - print STDERR "No changes.\n"; + print STDERR __("No changes.\n"); } return 0; } @@ -1244,7 +1252,7 @@ sub patch_update_cmd { @them = @mods; } else { - @them = list_and_choose({ PROMPT => 'Patch update', + @them = list_and_choose({ PROMPT => __('Patch update'), HEADER => $status_head, }, @mods); } @@ -1394,12 +1402,12 @@ sub patch_update_file { my $response = $1; my $no = $ix > 10 ? $ix - 10 : 0; while ($response eq '') { - my $extra = ""; $no = display_hunks(\@hunk, $no); if ($no < $num) { - $extra = " ( to see more)"; + print __("go to which hunk ( to see more)? "); + } else { + print __("go to which hunk? "); } - print "go to which hunk$extra? "; $response = ; if (!defined $response) { $response = ''; @@ -1436,7 +1444,7 @@ sub patch_update_file { elsif ($line =~ m|^/(.*)|) { my $regex = $1; if ($1 eq "") { - print colored $prompt_color, "search for regex? "; + print colored $prompt_color, __("search for regex? "); $regex = ; if (defined $regex) { chomp $regex; @@ -1459,7 +1467,7 @@ sub patch_update_file { $iy++; $iy = 0 if ($iy >= $num); if ($ix == $iy) { - error_msg "No hunk matches the given pattern\n"; + error_msg __("No hunk matches the given pattern\n"); last; } } @@ -1471,7 +1479,7 @@ sub patch_update_file { $ix--; } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } @@ -1480,7 +1488,7 @@ sub patch_update_file { $ix++; } else { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); } next; } @@ -1493,13 +1501,13 @@ sub patch_update_file { } } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } elsif ($line =~ /^j/) { if ($other !~ /j/) { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); next; } } @@ -1557,18 +1565,18 @@ sub diff_cmd { my @mods = list_modified('index-only'); @mods = grep { !($_->{BINARY}) } @mods; return if (!@mods); - my (@them) = list_and_choose({ PROMPT => 'Review diff', + my (@them) = list_and_choose({ PROMPT => __('Review diff'), IMMEDIATE => 1, HEADER => $status_head, }, @mods); return if (!@them); - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD'; system(qw(git diff -p --cached), $reference, '--', map { $_->{VALUE} } @them); } sub quit_cmd { - print "Bye.\n"; + print __("Bye.\n"); exit(0); } @@ -1591,32 +1599,32 @@ sub process_args { if ($1 eq 'reset') { $patch_mode = 'reset_head'; $patch_mode_revision = 'HEAD'; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); if ($arg ne '--') { $patch_mode_revision = $arg; $patch_mode = ($arg eq 'HEAD' ? 'reset_head' : 'reset_nothead'); - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } } elsif ($1 eq 'checkout') { - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); if ($arg eq '--') { $patch_mode = 'checkout_index'; } else { $patch_mode_revision = $arg; $patch_mode = ($arg eq 'HEAD' ? 'checkout_head' : 'checkout_nothead'); - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } } elsif ($1 eq 'stage' or $1 eq 'stash') { $patch_mode = $1; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } else { die "unknown --patch mode: $1"; } } else { $patch_mode = 'stage'; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } die "invalid argument $arg, expecting --" unless $arg eq "--"; @@ -1638,10 +1646,10 @@ sub main_loop { [ 'help', \&help_cmd, ], ); while (1) { - my ($it) = list_and_choose({ PROMPT => 'What now', + my ($it) = list_and_choose({ PROMPT => __('What now'), SINGLETON => 1, LIST_FLAT => 4, - HEADER => '*** Commands ***', + HEADER => __('*** Commands ***'), ON_EOF => \&quit_cmd, IMMEDIATE => 1 }, @cmd); if ($it) { From 5fa832640a627d6930df2194a28e9cc4aa4ef5c9 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:26 -0100 Subject: [PATCH 0074/1540] i18n: add--interactive: mark simple here-documents for translation Mark messages in here-documents without interpolation for translation. The here-document delimiter \EOF, which is the same as 'EOF', indicates that the text is to be treated literally without interpolation of its content. Unfortunately xgettext is not able to extract here-documents delimited with \EOF but it is with delimiter enclosed in single quotes. So change \EOF to 'EOF', although in this case does not make difference what variation of here-document to use since there is nothing to interpolate. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index a4b29840a9806f..88c8d9dd6b3849 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -638,7 +638,7 @@ sub list_and_choose { } sub singleton_prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a numbered item foo - select item based on unique prefix @@ -647,7 +647,7 @@ sub singleton_prompt_help_cmd { } sub prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a single item 3-5 - select a range of items @@ -1581,7 +1581,9 @@ sub quit_cmd { } sub help_cmd { - print colored $help_color, <<\EOF ; +# TRANSLATORS: please do not translate the command names +# 'status', 'update', 'revert', etc. + print colored $help_color, __ <<'EOF' ; status - show paths with changes update - add working tree state to the staged set of changes revert - revert staged set of changes back to the HEAD version From 13c58c1754393c17fb50b63ae9a3bc07195e520c Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:27 -0100 Subject: [PATCH 0075/1540] i18n: add--interactive: mark strings with interpolation for translation Since at this point Git::I18N.perl lacks support for Perl i18n placeholder substitution, use of sprintf following die or error_msg is necessary for placeholder substitution take place. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 88c8d9dd6b3849..c1d4eb81561192 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -614,12 +614,12 @@ sub list_and_choose { else { $bottom = $top = find_unique($choice, @stuff); if (!defined $bottom) { - error_msg "Huh ($choice)?\n"; + error_msg sprintf(__("Huh (%s)?\n"), $choice); next TOPLOOP; } } if ($opts->{SINGLETON} && $bottom != $top) { - error_msg "Huh ($choice)?\n"; + error_msg sprintf(__("Huh (%s)?\n"), $choice); next TOPLOOP; } for ($i = $bottom-1; $i <= $top-1; $i++) { @@ -716,7 +716,7 @@ sub revert_cmd { $_->{INDEX_ADDDEL} eq 'create') { system(qw(git update-index --force-remove --), $_->{VALUE}); - print "note: $_->{VALUE} is untracked now.\n"; + printf(__("note: %s is untracked now.\n"), $_->{VALUE}); } } } @@ -1053,7 +1053,7 @@ sub edit_hunk_manually { my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff"; my $fh; open $fh, '>', $hunkfile - or die "failed to open hunk edit file for writing: " . $!; + or die sprintf(__("failed to open hunk edit file for writing: %s"), $!); print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; my $participle = $patch_mode_flavour{PARTICIPLE}; @@ -1080,7 +1080,7 @@ sub edit_hunk_manually { } open $fh, '<', $hunkfile - or die "failed to open hunk edit file for reading: " . $!; + or die sprintf(__("failed to open hunk edit file for reading: %s"), $!); my @newtext = grep { !/^#/ } <$fh>; close $fh; unlink $hunkfile; @@ -1233,7 +1233,7 @@ sub apply_patch_for_checkout_commit { sub patch_update_cmd { my @all_mods = list_modified($patch_mode_flavour{FILTER}); - error_msg "ignoring unmerged: $_->{VALUE}\n" + error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE}) for grep { $_->{UNMERGED} } @all_mods; @all_mods = grep { !$_->{UNMERGED} } @all_mods; @@ -1415,7 +1415,8 @@ sub patch_update_file { chomp $response; } if ($response !~ /^\s*\d+\s*$/) { - error_msg "Invalid number: '$response'\n"; + error_msg sprintf(__("Invalid number: '%s'\n"), + $response); } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { @@ -1457,7 +1458,7 @@ sub patch_update_file { if ($@) { my ($err,$exp) = ($@, $1); $err =~ s/ at .*git-add--interactive line \d+, line \d+.*$//; - error_msg "Malformed search regexp $exp: $err\n"; + error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err); next; } my $iy = $ix; @@ -1622,18 +1623,18 @@ sub process_args { $patch_mode = $1; $arg = shift @ARGV or die __("missing --"); } else { - die "unknown --patch mode: $1"; + die sprintf(__("unknown --patch mode: %s"), $1); } } else { $patch_mode = 'stage'; $arg = shift @ARGV or die __("missing --"); } - die "invalid argument $arg, expecting --" - unless $arg eq "--"; + die sprintf(__("invalid argument %s, expecting --"), + $arg) unless $arg eq "--"; %patch_mode_flavour = %{$patch_modes{$patch_mode}}; } elsif ($arg ne "--") { - die "invalid argument $arg, expecting --"; + die sprintf(__("invalid argument %s, expecting --"), $arg); } } From 901707babc95950a6e6bd0fcea2070f63b704ef4 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:28 -0100 Subject: [PATCH 0076/1540] i18n: clean.c: match string with git-add--interactive.perl Change strings for help to match the ones in git-add--interactive.perl. The strings now represent one entry to translate each rather then two entries each different only by an ending newline character. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- builtin/clean.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin/clean.c b/builtin/clean.c index 0371010afbad54..d6bc3aaaea0dfd 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -287,11 +287,11 @@ static void pretty_print_menus(struct string_list *menu_list) static void prompt_help_cmd(int singleton) { clean_print_color(CLEAN_COLOR_HELP); - printf_ln(singleton ? + printf(singleton ? _("Prompt help:\n" "1 - select a numbered item\n" "foo - select item based on unique prefix\n" - " - (empty) select nothing") : + " - (empty) select nothing\n") : _("Prompt help:\n" "1 - select a single item\n" "3-5 - select a range of items\n" @@ -299,7 +299,7 @@ static void prompt_help_cmd(int singleton) "foo - select item based on unique prefix\n" "-... - unselect specified items\n" "* - choose all items\n" - " - (empty) finish selecting")); + " - (empty) finish selecting\n")); clean_print_color(CLEAN_COLOR_RESET); } @@ -508,7 +508,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || (is_single && bottom != top)) { clean_print_color(CLEAN_COLOR_ERROR); - printf_ln(_("Huh (%s)?"), (*ptr)->buf); + printf(_("Huh (%s)?\n"), (*ptr)->buf); clean_print_color(CLEAN_COLOR_RESET); continue; } @@ -774,7 +774,7 @@ static int ask_each_cmd(void) static int quit_cmd(void) { string_list_clear(&del_list, 0); - printf_ln(_("Bye.")); + printf(_("Bye.\n")); return MENU_RETURN_NO_LOOP; } From c4a85c3b8eef8c3b37f5103870e82894d9e5e7d0 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:29 -0100 Subject: [PATCH 0077/1540] i18n: add--interactive: mark plural strings Mark plural strings for translation. Unfold each action case in one entire sentence. Pass new keyword for xgettext to extract. Update test to include new subroutine __n() for plural strings handling. Update documentation to include a description of the new __n() subroutine. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- Makefile | 3 ++- git-add--interactive.perl | 27 ++++++++++++++++++--------- perl/Git/I18N.pm | 10 +++++++++- t/t0202/test.pl | 11 ++++++++++- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index df4f86bb3039a1..12b56cead71b37 100644 --- a/Makefile +++ b/Makefile @@ -2109,7 +2109,8 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ --keyword=_ --keyword=N_ --keyword="Q_:1,2" XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \ --keyword=gettextln --keyword=eval_gettextln -XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl +XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ + --keyword=__ --keyword="__n:1,2" LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh diff --git a/git-add--interactive.perl b/git-add--interactive.perl index c1d4eb81561192..04eea239a4a335 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -668,12 +668,18 @@ sub status_cmd { sub say_n_paths { my $did = shift @_; my $cnt = scalar @_; - print "$did "; - if (1 < $cnt) { - print "$cnt paths\n"; - } - else { - print "one path\n"; + if ($did eq 'added') { + printf(__n("added %d path\n", "added %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'updated') { + printf(__n("updated %d path\n", "updated %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'reverted') { + printf(__n("reverted %d path\n", "reverted %d paths\n", + $cnt), $cnt); + } else { + printf(__n("touched %d path\n", "touched %d paths\n", + $cnt), $cnt); } } @@ -1420,7 +1426,8 @@ sub patch_update_file { } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { - error_msg "Sorry, only $num hunks available.\n"; + error_msg sprintf(__n("Sorry, only %d hunk available.\n", + "Sorry, only %d hunks available.\n", $num), $num); } next; } @@ -1515,8 +1522,10 @@ sub patch_update_file { elsif ($other =~ /s/ && $line =~ /^s/) { my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) { - print colored $header_color, "Split into ", - scalar(@split), " hunks.\n"; + print colored $header_color, sprintf( + __n("Split into %d hunk.\n", + "Split into %d hunks.\n", + scalar(@split)), scalar(@split)); } splice (@hunk, $ix, 1, @split); $num = scalar @hunk; diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm index f889fd6da91d2d..617d8c2a17de00 100644 --- a/perl/Git/I18N.pm +++ b/perl/Git/I18N.pm @@ -13,7 +13,7 @@ BEGIN { } } -our @EXPORT = qw(__); +our @EXPORT = qw(__ __n); our @EXPORT_OK = @EXPORT; sub __bootstrap_locale_messages { @@ -44,6 +44,7 @@ BEGIN eval { __bootstrap_locale_messages(); *__ = \&Locale::Messages::gettext; + *__n = \&Locale::Messages::ngettext; 1; } or do { # Tell test.pl that we couldn't load the gettext library. @@ -51,6 +52,7 @@ BEGIN # Just a fall-through no-op *__ = sub ($) { $_[0] }; + *__n = sub ($$$) { $_[2] == 1 ? $_[0] : $_[1] }; }; } @@ -70,6 +72,8 @@ Git::I18N - Perl interface to Git's Gettext localizations printf __("The following error occurred: %s\n"), $error; + printf __n("commited %d file\n", "commited %d files\n", $files), $files; + =head1 DESCRIPTION Git's internal Perl interface to gettext via L. If @@ -87,6 +91,10 @@ it. L's gettext function if all goes well, otherwise our passthrough fallback function. +=head2 __n($$$) + +L's ngettext function or passthrough fallback function. + =head1 AUTHOR Evar ArnfjErE Bjarmason diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 2c10cb4693e28e..4101833a8e87e4 100755 --- a/t/t0202/test.pl +++ b/t/t0202/test.pl @@ -4,7 +4,7 @@ use strict; use warnings; use POSIX qw(:locale_h); -use Test::More tests => 8; +use Test::More tests => 11; use Git::I18N; my $has_gettext_library = $Git::I18N::__HAS_LIBRARY; @@ -31,6 +31,7 @@ # more gettext wrapper functions. my %prototypes = (qw( __ $ + __n $$$ )); while (my ($sub, $proto) = each %prototypes) { is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype"); @@ -46,6 +47,14 @@ my ($got, $expect) = (('TEST: A Perl test string') x 2); is(__($got), $expect, "Passing a string through __() in the C locale works"); + + my ($got_singular, $got_plural, $expect_singular, $expect_plural) = + (('TEST: 1 file', 'TEST: n files') x 2); + + is(__n($got_singular, $got_plural, 1), $expect_singular, + "Get singular string through __n() in C locale"); + is(__n($got_singular, $got_plural, 2), $expect_plural, + "Get plural string through __n() in C locale"); } # Test a basic message on different locales From 0539d5e6d535760a99694ff5d5592e46ed929d15 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:30 -0100 Subject: [PATCH 0078/1540] i18n: add--interactive: mark patch prompt for translation Mark prompt message assembled in place for translation, unfolding each use case for each entry in the %patch_modes hash table. Previously, this script relied on whether $patch_mode was set to run the command patch_update_cmd() or show status and loop the main loop. Now, it uses $cmd to indicate we must run patch_update_cmd() and $patch_mode is used to tell which flavor of the %patch_modes are we on. This is introduced in order to be able to mark and unfold the message prompt knowing in which context we are. The tracking of context was done previously by point %patch_mode_flavour hash table to the correct entry of %patch_modes, focusing only on value of %patch_modes. Now, we are also interested in the key ('staged', 'stash', 'checkout_head', ...). Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- Makefile | 2 +- git-add--interactive.perl | 54 +++++++++++++++++++++++++++++++++------ perl/Git/I18N.pm | 11 +++++++- t/t0202/test.pl | 5 +++- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 12b56cead71b37..c79aceb42d4531 100644 --- a/Makefile +++ b/Makefile @@ -2110,7 +2110,7 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \ --keyword=gettextln --keyword=eval_gettextln XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ - --keyword=__ --keyword="__n:1,2" + --keyword=__ --keyword=N__ --keyword="__n:1,2" LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 04eea239a4a335..745462da5324cd 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -92,6 +92,7 @@ sub colored { } # command line options +my $cmd; my $patch_mode; my $patch_mode_revision; @@ -172,7 +173,8 @@ sub colored { }, ); -my %patch_mode_flavour = %{$patch_modes{stage}}; +$patch_mode = 'stage'; +my %patch_mode_flavour = %{$patch_modes{$patch_mode}}; sub run_cmd_pipe { if ($^O eq 'MSWin32') { @@ -1308,6 +1310,44 @@ sub display_hunks { return $i; } +my %patch_update_prompt_modes = ( + stage => { + mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "), + }, + stash => { + mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_head => { + mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_nothead => { + mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), + }, + checkout_index => { + mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_head => { + mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_nothead => { + mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), + }, +); + sub patch_update_file { my $quit = 0; my ($ix, $num); @@ -1380,12 +1420,9 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, $patch_mode_flavour{VERB}, - ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : - $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' : - ' this hunk'), - $patch_mode_flavour{TARGET}, - " [y,n,q,a,d,/$other,?]? "; + print colored $prompt_color, + sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other); + my $line = prompt_single_character; last unless defined $line; if ($line) { @@ -1641,6 +1678,7 @@ sub process_args { die sprintf(__("invalid argument %s, expecting --"), $arg) unless $arg eq "--"; %patch_mode_flavour = %{$patch_modes{$patch_mode}}; + $cmd = 1; } elsif ($arg ne "--") { die sprintf(__("invalid argument %s, expecting --"), $arg); @@ -1677,7 +1715,7 @@ sub main_loop { process_args(); refresh(); -if ($patch_mode) { +if ($cmd) { patch_update_cmd(); } else { diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm index 617d8c2a17de00..c41425c8d07a4b 100644 --- a/perl/Git/I18N.pm +++ b/perl/Git/I18N.pm @@ -13,7 +13,7 @@ BEGIN { } } -our @EXPORT = qw(__ __n); +our @EXPORT = qw(__ __n N__); our @EXPORT_OK = @EXPORT; sub __bootstrap_locale_messages { @@ -54,6 +54,8 @@ BEGIN *__ = sub ($) { $_[0] }; *__n = sub ($$$) { $_[2] == 1 ? $_[0] : $_[1] }; }; + + sub N__($) { return shift; } } 1; @@ -74,6 +76,7 @@ Git::I18N - Perl interface to Git's Gettext localizations printf __n("commited %d file\n", "commited %d files\n", $files), $files; + =head1 DESCRIPTION Git's internal Perl interface to gettext via L. If @@ -95,6 +98,12 @@ passthrough fallback function. L's ngettext function or passthrough fallback function. +=head2 N__($) + +No-operation that only returns its argument. Use this if you want xgettext to +extract the text to the pot template but do not want to trigger retrival of the +translation at run time. + =head1 AUTHOR Evar ArnfjErE Bjarmason diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 4101833a8e87e4..2cbf7b95907384 100755 --- a/t/t0202/test.pl +++ b/t/t0202/test.pl @@ -4,7 +4,7 @@ use strict; use warnings; use POSIX qw(:locale_h); -use Test::More tests => 11; +use Test::More tests => 13; use Git::I18N; my $has_gettext_library = $Git::I18N::__HAS_LIBRARY; @@ -32,6 +32,7 @@ my %prototypes = (qw( __ $ __n $$$ + N__ $ )); while (my ($sub, $proto) = each %prototypes) { is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype"); @@ -55,6 +56,8 @@ "Get singular string through __n() in C locale"); is(__n($got_singular, $got_plural, 2), $expect_plural, "Get plural string through __n() in C locale"); + + is(N__($got), $expect, "Passing a string through N__() in the C locale works"); } # Test a basic message on different locales From 186b52c52a46f843207839ad1bc78b6a8716c929 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:31 -0100 Subject: [PATCH 0079/1540] i18n: add--interactive: i18n of help_patch_cmd Mark help message of help_patch_cmd for translation. The message must be unfolded to be free of variables so we can have high quality translations. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 54 +++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 745462da5324cd..1d267165fb59ab 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1186,15 +1186,53 @@ sub edit_hunk_loop { } } +my %help_patch_modes = ( + stage => N__( +"y - stage this hunk +n - do not stage this hunk +q - quit; do not stage this hunk or any of the remaining ones +a - stage this hunk and all later hunks in the file +d - do not stage this hunk or any of the later hunks in the file"), + stash => N__( +"y - stash this hunk +n - do not stash this hunk +q - quit; do not stash this hunk or any of the remaining ones +a - stash this hunk and all later hunks in the file +d - do not stash this hunk or any of the later hunks in the file"), + reset_head => N__( +"y - unstage this hunk +n - do not unstage this hunk +q - quit; do not unstage this hunk or any of the remaining ones +a - unstage this hunk and all later hunks in the file +d - do not unstage this hunk or any of the later hunks in the file"), + reset_nothead => N__( +"y - apply this hunk to index +n - do not apply this hunk to index +q - quit; do not apply this hunk or any of the remaining ones +a - apply this hunk and all later hunks in the file +d - do not apply this hunk or any of the later hunks in the file"), + checkout_index => N__( +"y - discard this hunk from worktree +n - do not discard this hunk from worktree +q - quit; do not discard this hunk or any of the remaining ones +a - discard this hunk and all later hunks in the file +d - do not discard this hunk or any of the later hunks in the file"), + checkout_head => N__( +"y - discard this hunk from index and worktree +n - do not discard this hunk from index and worktree +q - quit; do not discard this hunk or any of the remaining ones +a - discard this hunk and all later hunks in the file +d - do not discard this hunk or any of the later hunks in the file"), + checkout_nothead => N__( +"y - apply this hunk to index and worktree +n - do not apply this hunk to index and worktree +q - quit; do not apply this hunk or any of the remaining ones +a - apply this hunk and all later hunks in the file +d - do not apply this hunk or any of the later hunks in the file"), +); + sub help_patch_cmd { - my $verb = lc $patch_mode_flavour{VERB}; - my $target = $patch_mode_flavour{TARGET}; - print colored $help_color, < Date: Wed, 14 Dec 2016 11:54:32 -0100 Subject: [PATCH 0080/1540] i18n: add--interactive: mark edit_hunk_manually message for translation Mark message of edit_hunk_manually displayed in the editing file when user chooses 'e' option. The message had to be unfolded to allow translation of the $participle verb. Some messages end up being exactly the same for some use cases, but left it for easier change in the future, e.g., wanting to change wording of one particular use case. The comment character is now used according to the git configuration core.commentchar. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 52 +++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 1d267165fb59ab..9282dd5c638397 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1055,6 +1055,30 @@ sub color_diff { } @_; } +my %edit_hunk_manually_modes = ( + stage => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for staging."), + stash => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for stashing."), + reset_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for unstaging."), + reset_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), + checkout_index => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding"), + checkout_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding."), + checkout_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), +); + sub edit_hunk_manually { my ($oldtext) = @_; @@ -1062,22 +1086,24 @@ sub edit_hunk_manually { my $fh; open $fh, '>', $hunkfile or die sprintf(__("failed to open hunk edit file for writing: %s"), $!); - print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; + print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n"); print $fh @$oldtext; - my $participle = $patch_mode_flavour{PARTICIPLE}; my $is_reverse = $patch_mode_flavour{IS_REVERSE}; my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-'); - print $fh <; + my @newtext = grep { !/^$comment_line_char/ } <$fh>; close $fh; unlink $hunkfile; From ef84426308c4aa3837839566ba3185323e3cf117 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:33 -0100 Subject: [PATCH 0081/1540] i18n: add--interactive: remove %patch_modes entries Remove unnecessary entries from %patch_modes. After the i18n conversion, these entries are not used anymore. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 9282dd5c638397..d6d627feea893e 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -105,9 +105,6 @@ sub colored { DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stage', - TARGET => '', - PARTICIPLE => 'staging', FILTER => 'file-only', IS_REVERSE => 0, }, @@ -115,9 +112,6 @@ sub colored { DIFF => 'diff-index -p HEAD', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stash', - TARGET => '', - PARTICIPLE => 'stashing', FILTER => undef, IS_REVERSE => 0, }, @@ -125,9 +119,6 @@ sub colored { DIFF => 'diff-index -p --cached', APPLY => sub { apply_patch 'apply -R --cached', @_; }, APPLY_CHECK => 'apply -R --cached', - VERB => 'Unstage', - TARGET => '', - PARTICIPLE => 'unstaging', FILTER => 'index-only', IS_REVERSE => 1, }, @@ -135,9 +126,6 @@ sub colored { DIFF => 'diff-index -R -p --cached', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Apply', - TARGET => ' to index', - PARTICIPLE => 'applying', FILTER => 'index-only', IS_REVERSE => 0, }, @@ -145,9 +133,6 @@ sub colored { DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply -R', @_; }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from worktree', - PARTICIPLE => 'discarding', FILTER => 'file-only', IS_REVERSE => 1, }, @@ -155,9 +140,6 @@ sub colored { DIFF => 'diff-index -p', APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from index and worktree', - PARTICIPLE => 'discarding', FILTER => undef, IS_REVERSE => 1, }, @@ -165,9 +147,6 @@ sub colored { DIFF => 'diff-index -R -p', APPLY => sub { apply_patch_for_checkout_commit '', @_ }, APPLY_CHECK => 'apply', - VERB => 'Apply', - TARGET => ' to index and worktree', - PARTICIPLE => 'applying', FILTER => undef, IS_REVERSE => 0, }, From 55aa04423f14df3099466dc7eea4ca2d027b7d4c Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:34 -0100 Subject: [PATCH 0082/1540] i18n: add--interactive: mark status words for translation Mark words 'nothing', 'unchanged' and 'binary' used to display what has been staged or not, in "git add -i" status command. Alternatively one could mark N__('nothing') no-op in order to xgettext(1) extract the string and then trigger the translation at run time only with __($print->{FILE}), but that has the side effect of triggering retrieval of translations for the changes indicator too (e.g. +2/-1) which may or may not be a problem. To avoid that potential problem, mark only where there is certain to trigger translation only of those words but in this case we must also retrieve the translation for the eq tests, since the value assigned was of the translation, not the English source. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index d6d627feea893e..46dfc5276ca157 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -294,7 +294,7 @@ sub list_modified { my ($change, $bin); $file = unquote_path($file); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -303,7 +303,7 @@ sub list_modified { $data{$file} = { INDEX => $change, BINARY => $bin, - FILE => 'nothing', + FILE => __('nothing'), } } elsif (($adddel, $file) = @@ -319,7 +319,7 @@ sub list_modified { $file = unquote_path($file); my ($change, $bin); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -339,7 +339,7 @@ sub list_modified { $file = unquote_path($2); if (!exists $data{$file}) { $data{$file} = +{ - INDEX => 'unchanged', + INDEX => __('unchanged'), BINARY => 0, }; } @@ -354,10 +354,10 @@ sub list_modified { if ($only) { if ($only eq 'index-only') { - next if ($it->{INDEX} eq 'unchanged'); + next if ($it->{INDEX} eq __('unchanged')); } if ($only eq 'file-only') { - next if ($it->{FILE} eq 'nothing'); + next if ($it->{FILE} eq __('nothing')); } } push @return, +{ From a4dde4c4e9059416a6249dce3958ffce5a134e0f Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:35 -0100 Subject: [PATCH 0083/1540] i18n: send-email: mark strings for translation Mark strings often displayed to the user for translation. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 54 +++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index da81be40cb7f9a..06e64699b45547 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -28,6 +28,7 @@ use File::Spec::Functions qw(catfile); use Error qw(:try); use Git; +use Git::I18N; Getopt::Long::Configure qw/ pass_through /; @@ -797,12 +798,12 @@ sub file_declares_8bit_cte { } if (!defined $auto_8bit_encoding && scalar %broken_encoding) { - print "The following files are 8bit, but do not declare " . - "a Content-Transfer-Encoding.\n"; + print __("The following files are 8bit, but do not declare " . + "a Content-Transfer-Encoding.\n"); foreach my $f (sort keys %broken_encoding) { print " $f\n"; } - $auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ", + $auto_8bit_encoding = ask(__("Which 8bit encoding should I declare [UTF-8]? "), valid_re => qr/.{4}/, confirm_only => 1, default => "UTF-8"); } @@ -829,7 +830,7 @@ sub file_declares_8bit_cte { # But it's a no-op to run sanitize_address on an already sanitized address. $sender = sanitize_address($sender); -my $to_whom = "To whom should the emails be sent (if anyone)?"; +my $to_whom = __("To whom should the emails be sent (if anyone)?"); my $prompting = 0; if (!@initial_to && !defined $to_cmd) { my $to = ask("$to_whom ", @@ -859,7 +860,7 @@ sub expand_one_alias { if ($thread && !defined $initial_reply_to && $prompting) { $initial_reply_to = ask( - "Message-ID to be used as In-Reply-To for the first email (if any)? ", + __("Message-ID to be used as In-Reply-To for the first email (if any)? "), default => "", valid_re => qr/\@.*\./, confirm_only => 1); } @@ -918,7 +919,10 @@ sub validate_address { my $address = shift; while (!extract_valid_address($address)) { print STDERR "error: unable to extract a valid address from: $address\n"; - $_ = ask("What to do with this address? ([q]uit|[d]rop|[e]dit): ", + # TRANSLATORS: Make sure to include [q] [d] [e] in your + # translation. The program will only accept English input + # at this point. + $_ = ask(__("What to do with this address? ([q]uit|[d]rop|[e]dit): "), valid_re => qr/^(?:quit|q|drop|d|edit|e)/i, default => 'q'); if (/^d/i) { @@ -1293,17 +1297,23 @@ sub send_message { if ($needs_confirm eq "inform") { $confirm_unconfigured = 0; # squelch this message for the rest of this run $ask_default = "y"; # assume yes on EOF since user hasn't explicitly asked for confirmation - print " The Cc list above has been expanded by additional\n"; - print " addresses found in the patch commit message. By default\n"; - print " send-email prompts before sending whenever this occurs.\n"; - print " This behavior is controlled by the sendemail.confirm\n"; - print " configuration setting.\n"; - print "\n"; - print " For additional information, run 'git send-email --help'.\n"; - print " To retain the current behavior, but squelch this message,\n"; - print " run 'git config --global sendemail.confirm auto'.\n\n"; + print __ < qr/^(?:yes|y|no|n|quit|q|all|a)/i, default => $ask_default); die "Send this email reply required" unless defined $_; @@ -1405,7 +1415,7 @@ sub send_message { if ($quiet) { printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); } else { - print (($dry_run ? "Dry-" : "")."OK. Log says:\n"); + print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n")); if (!file_name_is_absolute($smtp_server)) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; @@ -1480,13 +1490,13 @@ sub send_message { $sauthor = sanitize_address($author); next if $suppress_cc{'author'}; next if $suppress_cc{'self'} and $sauthor eq $sender; - printf("(mbox) Adding cc: %s from line '%s'\n", + printf(__("(mbox) Adding cc: %s from line '%s'\n"), $1, $_) unless $quiet; push @cc, $1; } elsif (/^To:\s+(.*)$/i) { foreach my $addr (parse_address_line($1)) { - printf("(mbox) Adding to: %s from line '%s'\n", + printf(__("(mbox) Adding to: %s from line '%s'\n"), $addr, $_) unless $quiet; push @to, $addr; } @@ -1500,7 +1510,7 @@ sub send_message { } else { next if ($suppress_cc{'cc'}); } - printf("(mbox) Adding cc: %s from line '%s'\n", + printf(__("(mbox) Adding cc: %s from line '%s'\n"), $addr, $_) unless $quiet; push @cc, $addr; } @@ -1534,7 +1544,7 @@ sub send_message { # So let's support that, too. $input_format = 'lots'; if (@cc == 0 && !$suppress_cc{'cc'}) { - printf("(non-mbox) Adding cc: %s from line '%s'\n", + printf(__("(non-mbox) Adding cc: %s from line '%s'\n"), $_, $_) unless $quiet; push @cc, $_; } elsif (!defined $subject) { @@ -1557,7 +1567,7 @@ sub send_message { next if $suppress_cc{'bodycc'} and $what =~ /Cc/i; } push @cc, $c; - printf("(body) Adding cc: %s from line '%s'\n", + printf(__("(body) Adding cc: %s from line '%s'\n"), $c, $_) unless $quiet; } } From 464931053bba771c670fa52ed6ca2eea72a55109 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:36 -0100 Subject: [PATCH 0084/1540] i18n: send-email: mark warnings and errors for translation Mark warnings, errors and other messages for translation. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 06e64699b45547..00d234e11476f7 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -118,20 +118,20 @@ sub format_2822_time { my $localmin = $localtm[1] + $localtm[2] * 60; my $gmtmin = $gmttm[1] + $gmttm[2] * 60; if ($localtm[0] != $gmttm[0]) { - die "local zone differs from GMT by a non-minute interval\n"; + die __("local zone differs from GMT by a non-minute interval\n"); } if ((($gmttm[6] + 1) % 7) == $localtm[6]) { $localmin += 1440; } elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) { $localmin -= 1440; } elsif ($gmttm[6] != $localtm[6]) { - die "local time offset greater than or equal to 24 hours\n"; + die __("local time offset greater than or equal to 24 hours\n"); } my $offset = $localmin - $gmtmin; my $offhour = $offset / 60; my $offmin = abs($offset % 60); if (abs($offhour) >= 24) { - die ("local time offset greater than or equal to 24 hours\n"); + die __("local time offset greater than or equal to 24 hours\n"); } return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d", @@ -199,13 +199,13 @@ sub do_edit { map { system('sh', '-c', $editor.' "$@"', $editor, $_); if (($? & 127) || ($? >> 8)) { - die("the editor exited uncleanly, aborting everything"); + die(__("the editor exited uncleanly, aborting everything")); } } @_; } else { system('sh', '-c', $editor.' "$@"', $editor, @_); if (($? & 127) || ($? >> 8)) { - die("the editor exited uncleanly, aborting everything"); + die(__("the editor exited uncleanly, aborting everything")); } } } @@ -299,7 +299,7 @@ sub signal_handler { my $rc = GetOptions("h" => \$help, "dump-aliases" => \$dump_aliases); usage() unless $rc; -die "--dump-aliases incompatible with other options\n" +die __("--dump-aliases incompatible with other options\n") if !$help and $dump_aliases and @ARGV; $rc = GetOptions( "sender|from=s" => \$sender, @@ -362,7 +362,7 @@ sub signal_handler { usage(); } -die "Cannot run git format-patch from outside a repository\n" +die __("Cannot run git format-patch from outside a repository\n") if $format_patch and not $repo; # Now, let's fill any that aren't set in with defaults: @@ -617,7 +617,7 @@ sub is_format_patch_arg { } if (@rev_list_opts) { - die "Cannot run git format-patch from outside a repository\n" + die __("Cannot run git format-patch from outside a repository\n") unless $repo; push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts); } @@ -638,7 +638,7 @@ sub is_format_patch_arg { print $_,"\n" for (@files); } } else { - print STDERR "\nNo patch files specified!\n\n"; + print STDERR __("\nNo patch files specified!\n\n"); usage(); } @@ -730,7 +730,7 @@ sub get_patch_subject { $sender = $1; next; } elsif (/^(?:To|Cc|Bcc):/i) { - print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n"; + print __("To/Cc/Bcc fields are not interpreted yet, they have been ignored\n"); next; } print $c2 $_; @@ -739,7 +739,7 @@ sub get_patch_subject { close $c2; if ($summary_empty) { - print "Summary email is empty, skipping it\n"; + print __("Summary email is empty, skipping it\n"); $compose = -1; } } elsif ($annotate) { @@ -1316,7 +1316,7 @@ sub send_message { $_ = ask(__("Send this email? ([y]es|[n]o|[q]uit|[a]ll): "), valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i, default => $ask_default); - die "Send this email reply required" unless defined $_; + die __("Send this email reply required") unless defined $_; if (/^n/i) { return 0; } elsif (/^q/i) { @@ -1342,7 +1342,7 @@ sub send_message { } else { if (!defined $smtp_server) { - die "The required SMTP server is not properly defined." + die __("The required SMTP server is not properly defined.") } if ($smtp_encryption eq 'ssl') { @@ -1427,10 +1427,10 @@ sub send_message { } print $header, "\n"; if ($smtp) { - print "Result: ", $smtp->code, ' ', + print __("Result: "), $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n"; } else { - print "Result: OK\n"; + print __("Result: OK\n"); } } @@ -1703,7 +1703,7 @@ sub apply_transfer_encoding { $message = MIME::Base64::decode($message) if ($from eq 'base64'); - die "cannot send message as 7bit" + die __("cannot send message as 7bit") if ($to eq '7bit' and $message =~ /[^[:ascii:]]/); return $message if ($to eq '7bit' or $to eq '8bit'); @@ -1711,7 +1711,7 @@ sub apply_transfer_encoding { if ($to eq 'quoted-printable'); return MIME::Base64::encode($message, "\n") if ($to eq 'base64'); - die "invalid transfer encoding"; + die __("invalid transfer encoding"); } sub unique_email_list { From 3c5cd20c7d177f0d93532271f421037c1440a2ff Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:37 -0100 Subject: [PATCH 0085/1540] i18n: send-email: mark string with interpolation for translation Mark warnings, errors and other messages that are interpolated for translation. We call sprintf() before calling die() and in few other circumstances in order to replace the values on the placeholders. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 87 ++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 00d234e11476f7..7f3297cdfb076a 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -279,10 +279,13 @@ sub signal_handler { # tmp files from --compose if (defined $compose_filename) { if (-e $compose_filename) { - print "'$compose_filename' contains an intermediate version of the email you were composing.\n"; + printf __("'%s' contains an intermediate version ". + "of the email you were composing.\n"), + $compose_filename; } if (-e ($compose_filename . ".final")) { - print "'$compose_filename.final' contains the composed email.\n" + printf __("'%s.final' contains the composed email.\n"), + $compose_filename; } } @@ -431,7 +434,7 @@ sub read_config { my(%suppress_cc); if (@suppress_cc) { foreach my $entry (@suppress_cc) { - die "Unknown --suppress-cc field: '$entry'\n" + die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry) unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/; $suppress_cc{$entry} = 1; } @@ -460,7 +463,7 @@ sub read_config { if ($confirm_unconfigured) { $confirm = scalar %suppress_cc ? 'compose' : 'auto'; }; -die "Unknown --confirm setting: '$confirm'\n" +die sprintf(__("Unknown --confirm setting: '%s'\n"), $confirm) unless $confirm =~ /^(?:auto|cc|compose|always|never)/; # Debugging, print out the suppressions. @@ -492,16 +495,16 @@ sub split_addrs { sub parse_sendmail_alias { local $_ = shift; if (/"/) { - print STDERR "warning: sendmail alias with quotes is not supported: $_\n"; + printf STDERR __("warning: sendmail alias with quotes is not supported: %s\n"), $_; } elsif (/:include:/) { - print STDERR "warning: `:include:` not supported: $_\n"; + printf STDERR __("warning: `:include:` not supported: %s\n"), $_; } elsif (/[\/|]/) { - print STDERR "warning: `/file` or `|pipe` redirection not supported: $_\n"; + printf STDERR __("warning: `/file` or `|pipe` redirection not supported: %s\n"), $_; } elsif (/^(\S+?)\s*:\s*(.+)$/) { my ($alias, $addr) = ($1, $2); $aliases{$alias} = [ split_addrs($addr) ]; } else { - print STDERR "warning: sendmail line is not recognized: $_\n"; + printf STDERR __("warning: sendmail line is not recognized: %s\n"), $_; } } @@ -582,11 +585,11 @@ sub is_format_patch_arg { if (defined($format_patch)) { return $format_patch; } - die(< $repo->repo_path()) : tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; open my $c, ">", $compose_filename - or die "Failed to open for writing $compose_filename: $!"; + or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!); my $tpl_sender = $sender || $repoauthor || $repocommitter || ''; @@ -692,10 +696,10 @@ sub get_patch_subject { } open my $c2, ">", $compose_filename . ".final" - or die "Failed to open $compose_filename.final : " . $!; + or die sprintf(__("Failed to open %s.final: %s"), $compose_filename, $!); open $c, "<", $compose_filename - or die "Failed to open $compose_filename : " . $!; + or die sprintf(__("Failed to open %s: %s"), $compose_filename, $!); my $need_8bit_cte = file_has_nonascii($compose_filename); my $in_body = 0; @@ -769,7 +773,9 @@ sub ask { return $resp; } if ($confirm_only) { - my $yesno = $term->readline("Are you sure you want to use <$resp> [y/N]? "); + my $yesno = $term->readline( + # TRANSLATORS: please keep [y/N] as is. + sprintf(__("Are you sure you want to use <%s> [y/N]? "), $resp)); if (defined $yesno && $yesno =~ /y/i) { return $resp; } @@ -811,9 +817,9 @@ sub file_declares_8bit_cte { if (!$force) { for my $f (@files) { if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) { - die "Refusing to send because the patch\n\t$f\n" + die sprintf(__("Refusing to send because the patch\n\t%s\n" . "has the template subject '*** SUBJECT HERE ***'. " - . "Pass --force if you really want to send.\n"; + . "Pass --force if you really want to send.\n"), $f); } } } @@ -848,7 +854,7 @@ sub expand_aliases { sub expand_one_alias { my $alias = shift; if ($EXPANDED_ALIASES{$alias}) { - die "fatal: alias '$alias' expands to itself\n"; + die sprintf(__("fatal: alias '%s' expands to itself\n"), $alias); } local $EXPANDED_ALIASES{$alias} = 1; return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias; @@ -910,7 +916,7 @@ sub extract_valid_address { sub extract_valid_address_or_die { my $address = shift; $address = extract_valid_address($address); - die "error: unable to extract a valid address from: $address\n" + die sprintf(__("error: unable to extract a valid address from: %s\n"), $address) if !$address; return $address; } @@ -918,7 +924,7 @@ sub extract_valid_address_or_die { sub validate_address { my $address = shift; while (!extract_valid_address($address)) { - print STDERR "error: unable to extract a valid address from: $address\n"; + printf STDERR __("error: unable to extract a valid address from: %s\n"), $address; # TRANSLATORS: Make sure to include [q] [d] [e] in your # translation. The program will only accept English input # at this point. @@ -1223,7 +1229,7 @@ sub ssl_verify_params { return (SSL_verify_mode => SSL_VERIFY_PEER(), SSL_ca_file => $smtp_ssl_cert_path); } else { - die "CA path \"$smtp_ssl_cert_path\" does not exist"; + die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path); } } @@ -1386,14 +1392,14 @@ sub send_message { # supported commands $smtp->hello($smtp_domain); } else { - die "Server does not support STARTTLS! ".$smtp->message; + die sprintf(__("Server does not support STARTTLS! %s"), $smtp->message); } } } if (!$smtp) { - die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ", - "VALUES: server=$smtp_server ", + die __("Unable to initialize SMTP properly. Check config and use --smtp-debug."), + " VALUES: server=$smtp_server ", "encryption=$smtp_encryption ", "hello=$smtp_domain", defined $smtp_server_port ? " port=$smtp_server_port" : ""; @@ -1410,10 +1416,10 @@ sub send_message { $smtp->datasend("$line") or die $smtp->message; } $smtp->dataend() or die $smtp->message; - $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message; + $smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message; } if ($quiet) { - printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); + printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject); } else { print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n")); if (!file_name_is_absolute($smtp_server)) { @@ -1443,7 +1449,7 @@ sub send_message { $message_num = 0; foreach my $t (@files) { - open my $fh, "<", $t or die "can't open file $t"; + open my $fh, "<", $t or die sprintf(__("can't open file %s"), $t); my $author = undef; my $sauthor = undef; @@ -1665,18 +1671,18 @@ sub recipients_cmd { my @addresses = (); open my $fh, "-|", "$cmd \Q$file\E" - or die "($prefix) Could not execute '$cmd'"; + or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd); while (my $address = <$fh>) { $address =~ s/^\s*//g; $address =~ s/\s*$//g; $address = sanitize_address($address); next if ($address eq $sender and $suppress_cc{'self'}); push @addresses, $address; - printf("($prefix) Adding %s: %s from: '%s'\n", - $what, $address, $cmd) unless $quiet; + printf(__("(%s) Adding %s: %s from: '%s'\n"), + $prefix, $what, $address, $cmd) unless $quiet; } close $fh - or die "($prefix) failed to close pipe to '$cmd'"; + or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd); return @addresses; } @@ -1730,10 +1736,10 @@ sub unique_email_list { sub validate_patch { my $fn = shift; open(my $fh, '<', $fn) - or die "unable to open $fn: $!\n"; + or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { if (length($line) > 998) { - return "$.: patch contains a line longer than 998 characters"; + return sprintf(__("%s: patch contains a line longer than 998 characters"), $.); } } return; @@ -1749,10 +1755,11 @@ sub handle_backup { (substr($file, 0, $lastlen) eq $last) && ($suffix = substr($file, $lastlen)) !~ /^[a-z0-9]/i) { if (defined $known_suffix && $suffix eq $known_suffix) { - print "Skipping $file with backup suffix '$known_suffix'.\n"; + printf(__("Skipping %s with backup suffix '%s'.\n"), $file, $known_suffix); $skip = 1; } else { - my $answer = ask("Do you really want to send $file? (y|N): ", + # TRANSLATORS: please keep "[y|N]" as is. + my $answer = ask(sprintf(__("Do you really want to send %s? [y|N]: "), $file), valid_re => qr/^(?:y|n)/i, default => 'n'); $skip = ($answer ne 'y'); @@ -1780,7 +1787,7 @@ sub handle_backup_files { sub file_has_nonascii { my $fn = shift; open(my $fh, '<', $fn) - or die "unable to open $fn: $!\n"; + or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { return 1 if $line =~ /[^[:ascii:]]/; } @@ -1790,7 +1797,7 @@ sub file_has_nonascii { sub body_or_subject_has_nonascii { my $fn = shift; open(my $fh, '<', $fn) - or die "unable to open $fn: $!\n"; + or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { last if $line =~ /^$/; return 1 if $line =~ /^Subject.*[^[:ascii:]]/; From 70aedfb3ecef6660962d84cbd1455704cd9be5b8 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:38 -0100 Subject: [PATCH 0086/1540] i18n: send-email: mark composing message for translation When composing an e-mail, there is a message for the user whose lines begin in "GIT:" that can be marked for translation. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 7f3297cdfb076a..068d60b3e698f0 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -672,18 +672,20 @@ sub get_patch_subject { my $tpl_subject = $initial_subject || ''; my $tpl_reply_to = $initial_reply_to || ''; - print $c < Date: Wed, 14 Dec 2016 11:54:39 -0100 Subject: [PATCH 0087/1540] i18n: difftool: mark warnings for translation Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-difftool.perl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/git-difftool.perl b/git-difftool.perl index a5790d03a07588..8d3632e556690e 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -22,6 +22,7 @@ use File::Temp qw(tempdir); use Getopt::Long qw(:config pass_through); use Git; +use Git::I18N; sub usage { @@ -122,7 +123,7 @@ sub setup_dir_diff my $i = 0; while ($i < $#rawdiff) { if ($rawdiff[$i] =~ /^::/) { - warn << 'EOF'; + warn __ <<'EOF'; Combined diff formats ('-c' and '--cc') are not supported in directory diff mode ('-d' and '--dir-diff'). EOF @@ -338,7 +339,7 @@ sub main if (length($opts{difftool_cmd}) > 0) { $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd}; } else { - print "No given for --tool=\n"; + print __("No given for --tool=\n"); usage(1); } } @@ -346,7 +347,7 @@ sub main if (length($opts{extcmd}) > 0) { $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd}; } else { - print "No given for --extcmd=\n"; + print __("No given for --extcmd=\n"); usage(1); } } @@ -419,11 +420,11 @@ sub dir_diff } if (exists $wt_modified{$file} and exists $tmp_modified{$file}) { - my $errmsg = "warning: Both files modified: "; - $errmsg .= "'$workdir/$file' and '$b/$file'.\n"; - $errmsg .= "warning: Working tree file has been left.\n"; - $errmsg .= "warning:\n"; - warn $errmsg; + warn sprintf(__( + "warning: Both files modified:\n" . + "'%s/%s' and '%s/%s'.\n" . + "warning: Working tree file has been left.\n" . + "warning:\n"), $workdir, $file, $b, $file); $error = 1; } elsif (exists $tmp_modified{$file}) { my $mode = stat("$b/$file")->mode; @@ -435,8 +436,9 @@ sub dir_diff } } if ($error) { - warn "warning: Temporary files exist in '$tmpdir'.\n"; - warn "warning: You may want to cleanup or recover these.\n"; + warn sprintf(__( + "warning: Temporary files exist in '%s'.\n" . + "warning: You may want to cleanup or recover these.\n"), $tmpdir); exit(1); } else { exit_cleanup($tmpdir, $rc); From 85e42053658cf6710b84974be0ef0acf459a80d9 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 14 Dec 2016 14:39:50 -0800 Subject: [PATCH 0088/1540] lib-proto-disable: variable name fix The test_proto function assigns the positional parameters to named variables, but then still refers to "$desc" as "$1". Using $desc is more readable and less error-prone. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- t/lib-proto-disable.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh index b0917d93e64a93..be88e9a00f506c 100644 --- a/t/lib-proto-disable.sh +++ b/t/lib-proto-disable.sh @@ -9,7 +9,7 @@ test_proto () { proto=$2 url=$3 - test_expect_success "clone $1 (enabled)" ' + test_expect_success "clone $desc (enabled)" ' rm -rf tmp.git && ( GIT_ALLOW_PROTOCOL=$proto && @@ -18,7 +18,7 @@ test_proto () { ) ' - test_expect_success "fetch $1 (enabled)" ' + test_expect_success "fetch $desc (enabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=$proto && @@ -27,7 +27,7 @@ test_proto () { ) ' - test_expect_success "push $1 (enabled)" ' + test_expect_success "push $desc (enabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=$proto && @@ -36,7 +36,7 @@ test_proto () { ) ' - test_expect_success "push $1 (disabled)" ' + test_expect_success "push $desc (disabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=none && @@ -45,7 +45,7 @@ test_proto () { ) ' - test_expect_success "fetch $1 (disabled)" ' + test_expect_success "fetch $desc (disabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=none && @@ -54,7 +54,7 @@ test_proto () { ) ' - test_expect_success "clone $1 (disabled)" ' + test_expect_success "clone $desc (disabled)" ' rm -rf tmp.git && ( GIT_ALLOW_PROTOCOL=none && From f962ddf6edb199b2611d575a75f60d20d5c137c3 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 14 Dec 2016 14:39:51 -0800 Subject: [PATCH 0089/1540] http: always warn if libcurl version is too old Always warn if libcurl version is too old because: 1. Even without a protocol whitelist, newer versions of curl have all non-standard protocols disabled by default. 2. A future patch will introduce default "known-good" and "known-bad" protocols which are allowed/disallowed by 'is_transport_allowed' which older version of libcurl can't respect. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- http.c | 5 ++--- transport.c | 5 ----- transport.h | 6 ------ 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/http.c b/http.c index 5cd3ffd67f40e7..034426e746f81a 100644 --- a/http.c +++ b/http.c @@ -583,9 +583,8 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols); curl_easy_setopt(result, CURLOPT_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)"); + 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")) diff --git a/transport.c b/transport.c index 41eb82c6f3f4de..dff929ec01fdb8 100644 --- a/transport.c +++ b/transport.c @@ -629,11 +629,6 @@ void transport_check_allowed(const char *type) die("transport '%s' not allowed", type); } -int transport_restrict_protocols(void) -{ - return !!protocol_whitelist(); -} - struct transport *transport_get(struct remote *remote, const char *url) { const char *helper; diff --git a/transport.h b/transport.h index c68140892c6258..3396e1d434062b 100644 --- a/transport.h +++ b/transport.h @@ -153,12 +153,6 @@ int is_transport_allowed(const char *type); */ 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 f1762d772e9b415a3163abf5f217fc3b71a3b40e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 14 Dec 2016 14:39:52 -0800 Subject: [PATCH 0090/1540] transport: add protocol policy config option Previously the `GIT_ALLOW_PROTOCOL` environment variable was used to specify a whitelist of protocols to be used in clone/fetch/push commands. This patch introduces new configuration options for more fine-grained control for allowing/disallowing protocols. This also has the added benefit of allowing easier construction of a protocol whitelist on systems where setting an environment variable is non-trivial. Now users can specify a policy to be used for each type of protocol via the 'protocol..allow' config option. A default policy for all unconfigured protocols can be set with the 'protocol.allow' config option. If no user configured default is made git will allow known-safe protocols (http, https, git, ssh, file), disallow known-dangerous protocols (ext), and have a default policy of `user` for all other protocols. The supported policies are `always`, `never`, and `user`. The `user` policy can be used to configure a protocol to be usable when explicitly used by a user, while disallowing it for commands which run clone/fetch/push commands without direct user intervention (e.g. recursive initialization of submodules). Commands which can potentially clone/fetch/push from untrusted repositories without user intervention can export `GIT_PROTOCOL_FROM_USER` with a value of '0' to prevent protocols configured to the `user` policy from being used. Fix remote-ext tests to use the new config to allow the ext protocol to be tested. Based on a patch by Jeff King Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- Documentation/config.txt | 46 +++++++++++ Documentation/git.txt | 38 ++++----- git-submodule.sh | 12 +-- t/lib-proto-disable.sh | 130 +++++++++++++++++++++++++++++-- t/t5509-fetch-push-namespaces.sh | 1 + t/t5802-connect-helper.sh | 1 + transport.c | 75 +++++++++++++++++- 7 files changed, 264 insertions(+), 39 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 81533364352ec5..50d3d06ffa0a27 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2260,6 +2260,52 @@ pretty.:: Note that an alias with the same name as a built-in format will be silently ignored. +protocol.allow:: + If set, provide a user defined default policy for all protocols which + don't explicitly have a policy (`protocol..allow`). By default, + if unset, known-safe protocols (http, https, git, ssh, file) have a + default policy of `always`, known-dangerous protocols (ext) have a + default policy of `never`, and all other protocols have a default + policy of `user`. Supported policies: ++ +-- + +* `always` - protocol is always able to be used. + +* `never` - protocol is never able to be used. + +* `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is + either unset or has a value of 1. This policy should be used when you want a + protocol to be directly usable by the user but don't want it used by commands which + execute clone/fetch/push commands without user input, e.g. recursive + submodule initialization. + +-- + +protocol..allow:: + Set a policy to be used by protocol `` with clone/fetch/push + commands. See `protocol.allow` above for the available policies. ++ +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, + `ssh://`, etc). + + - `http`: git over http, both "smart http" and "dumb http". + Note that this does _not_ include `https`; if you want to configure + both, you must do so individually. + + - any external helpers are named by their protocol (e.g., use + `hg` to allow the `git-remote-hg` helper) +-- + pull.ff:: By default, Git does not create an extra merge commit when merging a commit that is a descendant of the current commit. Instead, the diff --git a/Documentation/git.txt b/Documentation/git.txt index 923aa49db7f67c..d9fb937586c8df 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -1129,30 +1129,20 @@ of clones and fetches. 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, - `ssh://`, etc). - - - `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) - + If set to a colon-separated list of protocols, behave as if + `protocol.allow` is set to `never`, and each of the listed + protocols has `protocol..allow` set to `always` + (overriding any existing configuration). In other words, any + protocol not mentioned will be disallowed (i.e., this is a + whitelist, not a blacklist). See the description of + `protocol.allow` in linkgit:git-config[1] for more details. + +`GIT_PROTOCOL_FROM_USER`:: + Set to 0 to prevent protocols used by fetch/push/clone which are + configured to the `user` state. This is useful to restrict recursive + submodule initialization from an untrusted repository or for programs + which feed potentially-untrusted URLS to git commands. See + linkgit:git-config[1] for more details. Discussion[[Discussion]] ------------------------ diff --git a/git-submodule.sh b/git-submodule.sh index 78fdac95684b22..fc440761af944b 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -22,14 +22,10 @@ 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 +# Tell the rest of git that any URLs we get don't come +# directly from the user, so it can apply policy as appropriate. +GIT_PROTOCOL_FROM_USER=0 +export GIT_PROTOCOL_FROM_USER command= branch= diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh index be88e9a00f506c..02f49cb4097153 100644 --- a/t/lib-proto-disable.sh +++ b/t/lib-proto-disable.sh @@ -1,10 +1,7 @@ # 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 () { +# Test clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist +test_whitelist () { desc=$1 proto=$2 url=$3 @@ -62,6 +59,129 @@ test_proto () { test_must_fail git clone --bare "$url" tmp.git ) ' + + test_expect_success "clone $desc (env var has precedence)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git -c protocol.allow=always clone --bare "$url" tmp.git && + test_must_fail git -c protocol.$proto.allow=always clone --bare "$url" tmp.git + ) + ' +} + +test_config () { + desc=$1 + proto=$2 + url=$3 + + # Test clone/fetch/push with protocol..allow config + test_expect_success "clone $desc (enabled with config)" ' + rm -rf tmp.git && + git -c protocol.$proto.allow=always clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=always fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=always push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + test_must_fail git -C tmp.git -c protocol.$proto.allow=never push origin HEAD:pushed + ' + + test_expect_success "fetch $desc (disabled)" ' + test_must_fail git -C tmp.git -c protocol.$proto.allow=never fetch + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + test_must_fail git -c protocol.$proto.allow=never clone --bare "$url" tmp.git + ' + + # Test clone/fetch/push with protocol.user.allow and its env var + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + git -c protocol.$proto.allow=user clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=user fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=user push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + ( + cd tmp.git && + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user push origin HEAD:pushed + ) + ' + + test_expect_success "fetch $desc (disabled)" ' + ( + cd tmp.git && + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user fetch + ) + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + ( + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user clone --bare "$url" tmp.git + ) + ' + + # Test clone/fetch/push with protocol.allow user defined default + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + git config --global protocol.allow always && + git clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + git config --global protocol.allow never && + test_must_fail git -C tmp.git push origin HEAD:pushed + ' + + test_expect_success "fetch $desc (disabled)" ' + test_must_fail git -C tmp.git fetch + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + test_must_fail git clone --bare "$url" tmp.git + ' +} + +# 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 () { + test_whitelist "$@" + + test_config "$@" } # set up an ssh wrapper that will access $host/$repo in the diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh index bc44ac36d57615..75c570adcae4f4 100755 --- a/t/t5509-fetch-push-namespaces.sh +++ b/t/t5509-fetch-push-namespaces.sh @@ -4,6 +4,7 @@ test_description='fetch/push involving ref namespaces' . ./test-lib.sh test_expect_success setup ' + git config --global protocol.ext.allow user && test_tick && git init original && ( diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh index b7a7f9d5886f1e..c6c2661878c0ca 100755 --- a/t/t5802-connect-helper.sh +++ b/t/t5802-connect-helper.sh @@ -4,6 +4,7 @@ test_description='ext::cmd remote "connect" helper' . ./test-lib.sh test_expect_success setup ' + git config --global protocol.ext.allow user && test_tick && git commit --allow-empty -m initial && test_tick && diff --git a/transport.c b/transport.c index dff929ec01fdb8..fbd799d062577d 100644 --- a/transport.c +++ b/transport.c @@ -617,10 +617,81 @@ static const struct string_list *protocol_whitelist(void) return enabled ? &allowed : NULL; } +enum protocol_allow_config { + PROTOCOL_ALLOW_NEVER = 0, + PROTOCOL_ALLOW_USER_ONLY, + PROTOCOL_ALLOW_ALWAYS +}; + +static enum protocol_allow_config parse_protocol_config(const char *key, + const char *value) +{ + if (!strcasecmp(value, "always")) + return PROTOCOL_ALLOW_ALWAYS; + else if (!strcasecmp(value, "never")) + return PROTOCOL_ALLOW_NEVER; + else if (!strcasecmp(value, "user")) + return PROTOCOL_ALLOW_USER_ONLY; + + die("unknown value for config '%s': %s", key, value); +} + +static enum protocol_allow_config get_protocol_config(const char *type) +{ + char *key = xstrfmt("protocol.%s.allow", type); + char *value; + + /* first check the per-protocol config */ + if (!git_config_get_string(key, &value)) { + enum protocol_allow_config ret = + parse_protocol_config(key, value); + free(key); + free(value); + return ret; + } + free(key); + + /* if defined, fallback to user-defined default for unknown protocols */ + if (!git_config_get_string("protocol.allow", &value)) { + enum protocol_allow_config ret = + parse_protocol_config("protocol.allow", value); + free(value); + return ret; + } + + /* fallback to built-in defaults */ + /* known safe */ + if (!strcmp(type, "http") || + !strcmp(type, "https") || + !strcmp(type, "git") || + !strcmp(type, "ssh") || + !strcmp(type, "file")) + return PROTOCOL_ALLOW_ALWAYS; + + /* known scary; err on the side of caution */ + if (!strcmp(type, "ext")) + return PROTOCOL_ALLOW_NEVER; + + /* unknown; by default let them be used only directly by the user */ + return PROTOCOL_ALLOW_USER_ONLY; +} + int is_transport_allowed(const char *type) { - const struct string_list *allowed = protocol_whitelist(); - return !allowed || string_list_has_string(allowed, type); + const struct string_list *whitelist = protocol_whitelist(); + if (whitelist) + return string_list_has_string(whitelist, type); + + switch (get_protocol_config(type)) { + case PROTOCOL_ALLOW_ALWAYS: + return 1; + case PROTOCOL_ALLOW_NEVER: + return 0; + case PROTOCOL_ALLOW_USER_ONLY: + return git_env_bool("GIT_PROTOCOL_FROM_USER", 1); + } + + die("BUG: invalid protocol_allow_config type"); } void transport_check_allowed(const char *type) From aeae4db1747d891dc3aee6a74508c585c321cc49 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 14 Dec 2016 14:39:53 -0800 Subject: [PATCH 0091/1540] http: create function to get curl allowed protocols Move the creation of an allowed protocols whitelist to a helper function. This will be useful when we need to compute the set of allowed protocols differently for normal and redirect cases. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- http.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/http.c b/http.c index 034426e746f81a..f7c488af8b0476 100644 --- a/http.c +++ b/http.c @@ -489,10 +489,25 @@ static void set_curl_keepalive(CURL *c) } #endif +static long get_curl_allowed_protocols(void) +{ + long allowed_protocols = 0; + + 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; + + return allowed_protocols; +} + static CURL *get_curl_handle(void) { CURL *result = curl_easy_init(); - long allowed_protocols = 0; if (!result) die("curl_easy_init failed"); @@ -572,16 +587,10 @@ static CURL *get_curl_handle(void) 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); - curl_easy_setopt(result, CURLOPT_PROTOCOLS, allowed_protocols); + curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, + get_curl_allowed_protocols()); + curl_easy_setopt(result, CURLOPT_PROTOCOLS, + get_curl_allowed_protocols()); #else warning("protocol restrictions not applied to curl redirects because\n" "your curl version is too old (>= 7.19.4)"); From a768a02265f3b8f43e37f66a0a3affba92c830c7 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 14 Dec 2016 14:39:54 -0800 Subject: [PATCH 0092/1540] transport: add from_user parameter to is_transport_allowed Add a from_user parameter to is_transport_allowed() to allow http to be able to distinguish between protocol restrictions for redirects versus initial requests. CURLOPT_REDIR_PROTOCOLS can now be set differently from CURLOPT_PROTOCOLS to disallow use of protocols with the "user" policy in redirects. This change allows callers to query if a transport protocol is allowed, given that the caller knows that the protocol is coming from the user (1) or not from the user (0) such as redirects in libcurl. If unknown a -1 should be provided which falls back to reading `GIT_PROTOCOL_FROM_USER` to determine if the protocol came from the user. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- http.c | 14 +++++++------- t/t5812-proto-disable-http.sh | 7 +++++++ transport.c | 8 +++++--- transport.h | 13 ++++++++++--- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/http.c b/http.c index f7c488af8b0476..2208269b33ae11 100644 --- a/http.c +++ b/http.c @@ -489,17 +489,17 @@ static void set_curl_keepalive(CURL *c) } #endif -static long get_curl_allowed_protocols(void) +static long get_curl_allowed_protocols(int from_user) { long allowed_protocols = 0; - if (is_transport_allowed("http")) + if (is_transport_allowed("http", from_user)) allowed_protocols |= CURLPROTO_HTTP; - if (is_transport_allowed("https")) + if (is_transport_allowed("https", from_user)) allowed_protocols |= CURLPROTO_HTTPS; - if (is_transport_allowed("ftp")) + if (is_transport_allowed("ftp", from_user)) allowed_protocols |= CURLPROTO_FTP; - if (is_transport_allowed("ftps")) + if (is_transport_allowed("ftps", from_user)) allowed_protocols |= CURLPROTO_FTPS; return allowed_protocols; @@ -588,9 +588,9 @@ static CURL *get_curl_handle(void) #endif #if LIBCURL_VERSION_NUM >= 0x071304 curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, - get_curl_allowed_protocols()); + get_curl_allowed_protocols(0)); curl_easy_setopt(result, CURLOPT_PROTOCOLS, - get_curl_allowed_protocols()); + get_curl_allowed_protocols(-1)); #else warning("protocol restrictions not applied to curl redirects because\n" "your curl version is too old (>= 7.19.4)"); diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index 044cc152f83b05..d911afd24cd7d4 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -30,5 +30,12 @@ test_expect_success 'curl limits redirects' ' test_must_fail git clone "$HTTPD_URL/loop-redir/smart/repo.git" ' +test_expect_success 'http can be limited to from-user' ' + git -c protocol.http.allow=user \ + clone "$HTTPD_URL/smart/repo.git" plain.git && + test_must_fail git -c protocol.http.allow=user \ + clone "$HTTPD_URL/smart-redir-perm/repo.git" redir.git +' + stop_httpd test_done diff --git a/transport.c b/transport.c index fbd799d062577d..f50c31a572f359 100644 --- a/transport.c +++ b/transport.c @@ -676,7 +676,7 @@ static enum protocol_allow_config get_protocol_config(const char *type) return PROTOCOL_ALLOW_USER_ONLY; } -int is_transport_allowed(const char *type) +int is_transport_allowed(const char *type, int from_user) { const struct string_list *whitelist = protocol_whitelist(); if (whitelist) @@ -688,7 +688,9 @@ int is_transport_allowed(const char *type) case PROTOCOL_ALLOW_NEVER: return 0; case PROTOCOL_ALLOW_USER_ONLY: - return git_env_bool("GIT_PROTOCOL_FROM_USER", 1); + if (from_user < 0) + from_user = git_env_bool("GIT_PROTOCOL_FROM_USER", 1); + return from_user; } die("BUG: invalid protocol_allow_config type"); @@ -696,7 +698,7 @@ int is_transport_allowed(const char *type) void transport_check_allowed(const char *type) { - if (!is_transport_allowed(type)) + if (!is_transport_allowed(type, -1)) die("transport '%s' not allowed", type); } diff --git a/transport.h b/transport.h index 3396e1d434062b..4f1c801994fc01 100644 --- a/transport.h +++ b/transport.h @@ -142,10 +142,17 @@ struct transport { 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 + * Check whether a transport is allowed by the environment. + * + * Type should generally be the URL scheme, as described in + * Documentation/git.txt + * + * from_user specifies if the transport was given by the user. If unknown pass + * a -1 to read from the environment to determine if the transport was given by + * the user. + * */ -int is_transport_allowed(const char *type); +int is_transport_allowed(const char *type, int from_user); /* * Check whether a transport is allowed by the environment, From abcbdc03895ff3f00280e54af11fee92d6877044 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 14 Dec 2016 14:39:55 -0800 Subject: [PATCH 0093/1540] http: respect protocol.*.allow=user for http-alternates The http-walker may fetch the http-alternates (or alternates) file from a remote in order to find more objects. This should count as a "not from the user" use of the protocol. But because we implement the redirection ourselves and feed the new URL to curl, it will use the CURLOPT_PROTOCOLS rules, not the more restrictive CURLOPT_REDIR_PROTOCOLS. The ideal solution would be for each curl request we make to know whether or not is directly from the user or part of an alternates redirect, and then set CURLOPT_PROTOCOLS as appropriate. However, that would require plumbing that information through all of the various layers of the http code. Instead, let's check the protocol at the source: when we are parsing the remote http-alternates file. The only downside is that if there's any mismatch between what protocol we think it is versus what curl thinks it is, it could violate the policy. To address this, we'll make the parsing err on the picky side, and only allow protocols that it can parse definitively. So for example, you can't elude the "http" policy by asking for "HTTP://", even though curl might handle it; we would reject it as unknown. The only unsafe case would be if you have a URL that starts with "http://" but curl interprets as another protocol. That seems like an unlikely failure mode (and we are still protected by our base CURLOPT_PROTOCOL setting, so the worst you could do is trigger one of https, ftp, or ftps). Signed-off-by: Jeff King Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- http-walker.c | 52 ++++++++++++++++++++++++++++++-------- t/t5550-http-fetch-dumb.sh | 10 ++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/http-walker.c b/http-walker.c index c2f81cd6af9d9d..b34b6ace7cd80a 100644 --- a/http-walker.c +++ b/http-walker.c @@ -3,6 +3,7 @@ #include "walker.h" #include "http.h" #include "list.h" +#include "transport.h" struct alt_base { char *base; @@ -160,6 +161,32 @@ static void prefetch(struct walker *walker, unsigned char *sha1) #endif } +static int is_alternate_allowed(const char *url) +{ + const char *protocols[] = { + "http", "https", "ftp", "ftps" + }; + int i; + + for (i = 0; i < ARRAY_SIZE(protocols); i++) { + const char *end; + if (skip_prefix(url, protocols[i], &end) && + starts_with(end, "://")) + break; + } + + if (i >= ARRAY_SIZE(protocols)) { + warning("ignoring alternate with unknown protocol: %s", url); + return 0; + } + if (!is_transport_allowed(protocols[i], 0)) { + warning("ignoring alternate with restricted protocol: %s", url); + return 0; + } + + return 1; +} + static void process_alternates_response(void *callback_data) { struct alternates_request *alt_req = @@ -274,17 +301,20 @@ static void process_alternates_response(void *callback_data) struct strbuf target = STRBUF_INIT; strbuf_add(&target, base, serverlen); strbuf_add(&target, data + i, posn - i - 7); - warning("adding alternate object store: %s", - target.buf); - newalt = xmalloc(sizeof(*newalt)); - newalt->next = NULL; - newalt->base = strbuf_detach(&target, NULL); - newalt->got_indices = 0; - newalt->packs = NULL; - - while (tail->next != NULL) - tail = tail->next; - tail->next = newalt; + + if (is_alternate_allowed(target.buf)) { + warning("adding alternate object store: %s", + target.buf); + newalt = xmalloc(sizeof(*newalt)); + newalt->next = NULL; + newalt->base = strbuf_detach(&target, NULL); + newalt->got_indices = 0; + newalt->packs = NULL; + + while (tail->next != NULL) + tail = tail->next; + tail->next = newalt; + } } } i = posn + 1; diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 22011f0b68870a..c0ee29c65df256 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -360,5 +360,15 @@ test_expect_success 'http-alternates cannot point at funny protocols' ' clone "$HTTPD_URL/dumb/evil.git" evil-file ' +test_expect_success 'http-alternates triggers not-from-user protocol check' ' + echo "$HTTPD_URL/dumb/victim.git/objects" \ + >"$evil/objects/info/http-alternates" && + test_config_global http.followRedirects true && + test_must_fail git -c protocol.http.allow=user \ + clone $HTTPD_URL/dumb/evil.git evil-user && + git -c protocol.http.allow=always \ + clone $HTTPD_URL/dumb/evil.git evil-user +' + stop_httpd test_done From c7d227df5bf7fe9d5df98a55cd637bfaf38685ea Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 15 Dec 2016 12:43:46 -0500 Subject: [PATCH 0094/1540] merge: mark usage error strings for translation The nearby error messages are already marked for translation, but these new ones aren't. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/merge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/merge.c b/builtin/merge.c index 668aaffb849d5c..599d25c4ccb71c 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1164,7 +1164,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *nargv[] = {"reset", "--merge", NULL}; if (orig_argc != 2) - usage_msg_opt("--abort expects no arguments", + usage_msg_opt(_("--abort expects no arguments"), builtin_merge_usage, builtin_merge_options); if (!file_exists(git_path_merge_head())) @@ -1180,7 +1180,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *nargv[] = {"commit", NULL}; if (orig_argc != 2) - usage_msg_opt("--continue expects no arguments", + usage_msg_opt(_("--continue expects no arguments"), builtin_merge_usage, builtin_merge_options); if (!file_exists(git_path_merge_head())) From fbfda15fb8daf7388a19b8bed2f319a8c1b6c5f1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 11 Oct 2016 11:45:58 -0700 Subject: [PATCH 0095/1540] shortlog: group by committer information In some situations you may want to group the commits not by author, but by committer instead. For example, when I just wanted to look up what I'm still missing from linux-next in the current merge window, I don't care so much about who wrote a patch, as what git tree it came from, which generally boils down to "who committed it". So make git shortlog take a "-c" or "--committer" option to switch grouping to that. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 15 ++++++++++++--- shortlog.h | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/builtin/shortlog.c b/builtin/shortlog.c index ba0e1154a9f0b9..c9585d475d90c4 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -117,11 +117,15 @@ static void read_from_stdin(struct shortlog *log) { struct strbuf author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; + static const char *author_match[2] = { "Author: ", "author " }; + static const char *committer_match[2] = { "Commit: ", "committer " }; + const char **match; + match = log->committer ? committer_match : author_match; while (strbuf_getline_lf(&author, stdin) != EOF) { const char *v; - if (!skip_prefix(author.buf, "Author: ", &v) && - !skip_prefix(author.buf, "author ", &v)) + if (!skip_prefix(author.buf, match[0], &v) && + !skip_prefix(author.buf, match[1], &v)) continue; while (strbuf_getline_lf(&oneline, stdin) != EOF && oneline.len) @@ -140,6 +144,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) struct strbuf author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; struct pretty_print_context ctx = {0}; + const char *fmt; ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; @@ -148,7 +153,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - format_commit_message(commit, "%an <%ae>", &author, &ctx); + fmt = log->committer ? "%cn <%ce>" : "%an <%ae>"; + + format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); @@ -238,6 +245,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; const struct option options[] = { + OPT_BOOL('c', "committer", &log.committer, + N_("Group by committer rather than author")), OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, diff --git a/shortlog.h b/shortlog.h index 5a326c68602610..5d64cfe9296848 100644 --- a/shortlog.h +++ b/shortlog.h @@ -13,6 +13,7 @@ struct shortlog { int in2; int user_format; int abbrev; + int committer; char *common_repo_prefix; int email; From 03f40829ad22deb7b6950cfc28a3779ece9e6a56 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 16 Dec 2016 08:51:41 -0500 Subject: [PATCH 0096/1540] shortlog: test and document --committer option This puts the final touches on the feature added by fbfda15fb8 (shortlog: group by committer information, 2016-10-11). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-shortlog.txt | 4 ++++ t/t4201-shortlog.sh | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 31af7f27360d27..ee6c5476c1d2bf 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -47,6 +47,10 @@ OPTIONS Each pretty-printed commit will be rewrapped before it is shown. +-c:: +--committer:: + Collect and show committer identities instead of authors. + -w[[,[,]]]:: Linewrap the output by wrapping each line at `width`. The first line of each entry is indented by `indent1` spaces, and the second diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index ae08b57712e382..6c7c6374812a76 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -190,4 +190,17 @@ test_expect_success 'shortlog with --output=' ' test_line_count = 3 shortlog ' +test_expect_success 'shortlog --committer (internal)' ' + cat >expect <<-\EOF && + 3 C O Mitter + EOF + git shortlog -nsc HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'shortlog --committer (external)' ' + git log --format=full | git shortlog -nsc >actual && + test_cmp expect actual +' + test_done From eff96d7e16a769dca9b0319ccf460a83f514676e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 16 Dec 2016 15:30:13 -0800 Subject: [PATCH 0097/1540] First batch for 2.12 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 35 ++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index 2d68e987cc202d..01db423a92c0a5 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -20,7 +20,7 @@ Updates since v2.11 UI, Workflows & Features - * + * Various updates to "git p4". Performance, Internal Implementation, Development Support etc. @@ -50,4 +50,37 @@ notes for details). some configuration variable it uses were not documented. (merge ea9a93dcc2 ew/svn-fixes later to maint). + * "git rev-parse --symbolic" failed with a more recent notation like + "HEAD^-1" and "HEAD^!". + (merge a2e7b04c44 jk/rev-parse-symbolic-parents-fix later to maint). + + * An empty directory in a working tree that can simply be nuked used + to interfere while merging or cherry-picking a change to create a + submodule directory there, which has been fixed.. + (merge 5423d2e700 dt/empty-submodule-in-merge later to maint). + + * The code in "git push" to compute if any commit being pushed in the + superproject binds a commit in a submodule that hasn't been pushed + out was overly inefficient, making it unusable even for a small + project that does not have any submodule but have a reasonable + number of refs. + (merge 250ab24ab3 hv/submodule-not-yet-pushed-fix later to maint). + + * "git push --dry-run --recurse-submodule=on-demand" wasn't + "--dry-run" in the submodules. + (merge 0301c821c5 bw/push-dry-run later to maint). + + * The output from "git worktree list" was made in readdir() order, + and was unstable. + (merge 4df1d4d466 nd/worktree-list-fixup later to maint). + + * mergetool..trustExitCode configuration variable did not apply + to built-in tools, but now it does. + (merge 2967284456 da/mergetool-trust-exit-code later to maint). + + * "git p4" LFS support was broken when LFS stores an empty blob. + (merge d5eb3cf5e7 ls/p4-empty-file-on-lfs later to maint). + * Other minor doc, test and build updates and code cleanups. + (merge fa6ca11105 nd/qsort-in-merge-recursive later to maint). + (merge fa3142c919 ak/lazy-prereq-mktemp later to maint). From 6610af872f6494a061780ec738c8713a034b848b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Dec 2016 14:50:31 -0800 Subject: [PATCH 0098/1540] Second batch for 2.12 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 93 ++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index 01db423a92c0a5..c8b7fbb700e9dc 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -22,16 +22,42 @@ UI, Workflows & Features * Various updates to "git p4". + * "git p4" didn't interact with the internal of .git directory + correctly in the modern "git-worktree"-enabled world. + + * "git branch --list" and friends learned "--ignore-case" option to + optionally sort branches and tags case insensitively. + + * In addition to %(subject), %(body), "log --pretty=format:..." + learned a new placeholder %(trailers). + + * "git rebase" learned "--quit" option, which allows a user to + remove the metadata left by an earlier "git rebase" that was + manually aborted without using "git rebase --abort". + Performance, Internal Implementation, Development Support etc. - * + * Commands that operate on a log message and add lines to the trailer + blocks, such as "format-patch -s", "cherry-pick (-x|-s)", and + "commit -s", have been taught to use the logic of and share the + code with "git interpret-trailer". + * The default Travis-CI configuration specifies newer P4 and GitLFS. + (merge 5f703e8f02 ls/travis-update-p4-and-lfs later to maint). -Also contains various documentation updates and code clean-ups. + * The "fast hash" that had disastrous performance issues in some + corner cases has been retired from the internal diff. - * + * The character width table has been updated to match Unicode 9.0 + (merge 9e6e9aefdf bb/unicode-9.0 later to maint). + * Update the procedure to generate "tags" for developer support. + (merge 046e4c1c09 jk/make-tags-find-sources-tweak later to maint). + + + +Also contains various documentation updates and code clean-ups. Fixes since v2.10 ----------------- @@ -81,6 +107,67 @@ notes for details). * "git p4" LFS support was broken when LFS stores an empty blob. (merge d5eb3cf5e7 ls/p4-empty-file-on-lfs later to maint). + * A corner case in merge-recursive regression that crept in + during 2.10 development cycle has been fixed. + (merge 1c25d2d8ed jc/renormalize-merge-kill-safer-crlf later to maint). + + * Transport with dumb http can be fooled into following foreign URLs + that the end user does not intend to, especially with the server + side redirects and http-alternates mechanism, which can lead to + security issues. Tighten the redirection and make it more obvious + to the end user when it happens. + (merge cb4d2d35c4 jk/http-walker-limit-redirect-2.9 later to maint). + + * Update the error messages from the dumb-http client when it fails + to obtain loose objects; we used to give sensible error message + only upon 404 but we now forbid unexpected redirects that needs to + be reported with something sensible. + (merge 3680f16f9d jk/http-walker-limit-redirect later to maint). + + * When diff.renames configuration is on (and with Git 2.9 and later, + it is enabled by default, which made it worse), "git stash" + misbehaved if a file is removed and another file with a very + similar content is added. + (merge 9d4e28ead5 jk/stash-disable-renames-internally later to maint). + + * "git diff --no-index" did not take "--no-abbrev" option. + (merge 43d1948b7b jb/diff-no-index-no-abbrev later to maint). + + * "git difftool --dir-diff" had a minor regression when started from + a subdirectory, which has been fixed. + (merge 853e10c197 da/difftool-dir-diff-fix later to maint). + + * "git commit --allow-empty --only" (no pathspec) with dirty index + ought to be an acceptable way to create a new commit that does not + change any paths, but it was forbidden, perhaps because nobody + needed it so far. + (merge beb635ca9c ak/commit-only-allow-empty later to maint). + + * Git 2.11 had a minor regression in "merge --ff-only" that competed + with another process that simultanously attempted to update the + index. We used to explain what went wrong with an error message, + but the new code silently failed. The error message has been + resurrected. + + * A pathname that begins with "//" or "\\" on Windows is special but + path normalization logic was unaware of it. + (merge 7814fbe3f1 js/normalize-path-copy-ceil later to maint). + + * "git pull --rebase", when there is no new commits on our side since + we forked from the upstream, should be able to fast-forward without + invoking "git rebase", but it didn't. + (merge 33b842a1e9 jc/pull-rebase-ff later to maint). + + * The way to specify hotkeys to "xxdiff" that is used by "git + mergetool" has been modernized to match recent versions of xxdiff. + (merge 6cf5f6cef7 da/mergetool-xxdiff-hotkey later to maint). + * Other minor doc, test and build updates and code cleanups. (merge fa6ca11105 nd/qsort-in-merge-recursive later to maint). (merge fa3142c919 ak/lazy-prereq-mktemp later to maint). + (merge 9c48b4fb23 ls/t0021-fixup later to maint). + (merge 584f99c87b sb/unpack-trees-grammofix later to maint). + (merge 54471fdcc3 jk/readme-gmane-is-no-more later to maint). + (merge 9e189f1a5c sb/t3600-cleanup later to maint). + (merge e2c20be57c lr/doc-fix-cet later to maint). + (merge 47437fd3bd kh/tutorial-grammofix later to maint). From acefe2be287b099886f1240bd1f5adfa66dbadee Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Dec 2016 09:36:23 -0800 Subject: [PATCH 0099/1540] i18n: fix misconversion in shell scripts An earlier series that was merged at 2703572b3a ("Merge branch 'va/i18n-even-more'", 2016-07-13) failed to use $(eval_gettext "string with \$variable interpolation") and instead used gettext in a few places, and ended up showing the variable names in the message, e.g. $ git submodule fatal: $program_name cannot be used without a working tree. Catch these mistakes with $ git grep -n '[^_]gettext .*\\\$' and fix them all to use eval_gettext instead. Reported-by: Josh Bleecher Snyder Acked-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 3 ++- git-sh-setup.sh | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a545d92c269618..c5806859f0d475 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -467,7 +467,8 @@ update_squash_messages () { }' <"$squash_msg".bak } >"$squash_msg" else - commit_message HEAD > "$fixup_msg" || die "$(gettext "Cannot write \$fixup_msg")" + commit_message HEAD >"$fixup_msg" || + die "$(eval_gettext "Cannot write \$fixup_msg")" count=2 { printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")" diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 2eda134800b08e..c7b2a95463fd15 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -194,14 +194,14 @@ require_work_tree_exists () { if test "z$(git rev-parse --is-bare-repository)" != zfalse then program_name=$0 - die "$(gettext "fatal: \$program_name cannot be used without a working tree.")" + die "$(eval_gettext "fatal: \$program_name cannot be used without a working tree.")" fi } require_work_tree () { test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || { program_name=$0 - die "$(gettext "fatal: \$program_name cannot be used without a working tree.")" + die "$(eval_gettext "fatal: \$program_name cannot be used without a working tree.")" } } From 862f9312b3c910e5c8df6f96591fd3565d5a64ee Mon Sep 17 00:00:00 2001 From: Lars Schneider Date: Sun, 18 Dec 2016 20:01:40 +0100 Subject: [PATCH 0100/1540] git-p4: add diff/merge properties to .gitattributes for GitLFS files The `git lfs track` command generates a .gitattributes file with diff and merge properties [1]. Set the same .gitattributes format for files tracked with GitLFS in git-p4. [1] https://github.com/git-lfs/git-lfs/blob/v1.5.3/commands/command_track.go#L121 Signed-off-by: Lars Schneider Reviewed-by: Luke Diamand Signed-off-by: Junio C Hamano --- git-p4.py | 4 ++-- t/t9824-git-p4-git-lfs.sh | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/git-p4.py b/git-p4.py index e752153f6f1624..fd3763b6540549 100755 --- a/git-p4.py +++ b/git-p4.py @@ -1098,10 +1098,10 @@ def generateGitAttributes(self): '# Git LFS (see https://git-lfs.github.com/)\n', '#\n', ] + - ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n' + ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs diff=lfs merge=lfs -text\n' for f in sorted(gitConfigList('git-p4.largeFileExtensions')) ] + - ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n' + ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs diff=lfs merge=lfs -text\n' for f in sorted(self.largeFiles) if not self.hasLargeFileExtension(f) ] ) diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index ca93ac8813c6bc..54ab0770015ec4 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -81,9 +81,9 @@ test_expect_success 'Store files in LFS based on size (>24 bytes)' ' # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /file4.bin filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /file2.dat filter=lfs diff=lfs merge=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -109,7 +109,7 @@ test_expect_success 'Store files in LFS based on size (>25 bytes)' ' # # Git LFS (see https://git-lfs.github.com/) # - /file4.bin filter=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -135,7 +135,7 @@ test_expect_success 'Store files in LFS based on extension (dat)' ' # # Git LFS (see https://git-lfs.github.com/) # - *.dat filter=lfs -text + *.dat filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -163,8 +163,8 @@ test_expect_success 'Store files in LFS based on size (>25 bytes) and extension # # Git LFS (see https://git-lfs.github.com/) # - *.dat filter=lfs -text - /file4.bin filter=lfs -text + *.dat filter=lfs diff=lfs merge=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -199,8 +199,8 @@ test_expect_success 'Remove file from repo and store files in LFS based on size # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /file2.dat filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -237,8 +237,8 @@ test_expect_success 'Add .gitattributes and store files in LFS based on size (>2 # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /file2.dat filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -278,7 +278,7 @@ test_expect_success 'Add big files to repo and store files in LFS based on compr # # Git LFS (see https://git-lfs.github.com/) # - /file6.bin filter=lfs -text + /file6.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes From 29647d79a9a29498675ccb137f567a44dc9628b8 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, 19 Dec 2016 16:21:55 +0700 Subject: [PATCH 0101/1540] config.c: handle error case for fstat() calls 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 --- config.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/config.c b/config.c index d7ce34b33d67c3..19ceb2639ea236 100644 --- a/config.c +++ b/config.c @@ -2107,7 +2107,12 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, goto out_free; } - fstat(in_fd, &st); + if (fstat(in_fd, &st) == -1) { + error_errno(_("fstat on %s failed"), config_filename); + ret = CONFIG_INVALID_FILE; + goto out_free; + } + contents_sz = xsize_t(st.st_size); contents = xmmap_gently(NULL, contents_sz, PROT_READ, MAP_PRIVATE, in_fd, 0); @@ -2327,7 +2332,10 @@ int git_config_rename_section_in_file(const char *config_filename, goto unlock_and_out; } - fstat(fileno(config_file), &st); + if (fstat(fileno(config_file), &st) == -1) { + ret = error_errno(_("fstat on %s failed"), config_filename); + goto out; + } if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { ret = error_errno("chmod on %s failed", From 6e45b43fa9c17124c5aea03b3fd6563cf03bae07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Tue, 20 Dec 2016 16:48:35 +0700 Subject: [PATCH 0102/1540] config.c: rename label unlock_and_out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are two ways to unlock a file: commit, or revert. Rename it to commit_and_out to avoid confusion. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.c b/config.c index 19ceb2639ea236..8065296a116e64 100644 --- a/config.c +++ b/config.c @@ -2329,7 +2329,7 @@ int git_config_rename_section_in_file(const char *config_filename, if (!(config_file = fopen(config_filename, "rb"))) { /* no config file means nothing to rename, no error */ - goto unlock_and_out; + goto commit_and_out; } if (fstat(fileno(config_file), &st) == -1) { @@ -2391,7 +2391,7 @@ int git_config_rename_section_in_file(const char *config_filename, } } fclose(config_file); -unlock_and_out: +commit_and_out: if (commit_lock_file(lock) < 0) ret = error_errno("could not write config file %s", config_filename); From 14c01bdbe8db0a9c9ee228b5b9b5b816c0a38fbd Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 19 Dec 2016 10:25:31 -0800 Subject: [PATCH 0103/1540] transport: reformat flag #defines to be more readable All of the #defines for the TRANSPORT_* flags are hardcoded to be powers of two. This can be error prone when adding a new flag and is difficult to read. Update these defines to instead use a shift operation to generate the flags and reformat them. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- transport.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/transport.h b/transport.h index b8e4ee8099260a..1b654583676c62 100644 --- a/transport.h +++ b/transport.h @@ -131,21 +131,21 @@ struct transport { enum transport_family family; }; -#define TRANSPORT_PUSH_ALL 1 -#define TRANSPORT_PUSH_FORCE 2 -#define TRANSPORT_PUSH_DRY_RUN 4 -#define TRANSPORT_PUSH_MIRROR 8 -#define TRANSPORT_PUSH_PORCELAIN 16 -#define TRANSPORT_PUSH_SET_UPSTREAM 32 -#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64 -#define TRANSPORT_PUSH_PRUNE 128 -#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256 -#define TRANSPORT_PUSH_NO_HOOK 512 -#define TRANSPORT_PUSH_FOLLOW_TAGS 1024 -#define TRANSPORT_PUSH_CERT_ALWAYS 2048 -#define TRANSPORT_PUSH_CERT_IF_ASKED 4096 -#define TRANSPORT_PUSH_ATOMIC 8192 -#define TRANSPORT_PUSH_OPTIONS 16384 +#define TRANSPORT_PUSH_ALL (1<<0) +#define TRANSPORT_PUSH_FORCE (1<<1) +#define TRANSPORT_PUSH_DRY_RUN (1<<2) +#define TRANSPORT_PUSH_MIRROR (1<<3) +#define TRANSPORT_PUSH_PORCELAIN (1<<4) +#define TRANSPORT_PUSH_SET_UPSTREAM (1<<5) +#define TRANSPORT_RECURSE_SUBMODULES_CHECK (1<<6) +#define TRANSPORT_PUSH_PRUNE (1<<7) +#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND (1<<8) +#define TRANSPORT_PUSH_NO_HOOK (1<<9) +#define TRANSPORT_PUSH_FOLLOW_TAGS (1<<10) +#define TRANSPORT_PUSH_CERT_ALWAYS (1<<11) +#define TRANSPORT_PUSH_CERT_IF_ASKED (1<<12) +#define TRANSPORT_PUSH_ATOMIC (1<<13) +#define TRANSPORT_PUSH_OPTIONS (1<<14) extern int transport_summary_width(const struct ref *refs); From 6c656c3fe4ba13cdf03ed85c059690653fd376cb Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 19 Dec 2016 10:25:32 -0800 Subject: [PATCH 0104/1540] submodules: add RECURSE_SUBMODULES_ONLY value Add the `RECURSE_SUBMODULES_ONLY` enum value to submodule.h. This enum value will be used in a later patch to push to indicate that only submodules should be pushed, while the superproject should remain unpushed. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- submodule-config.c | 2 ++ submodule.h | 1 + 2 files changed, 3 insertions(+) diff --git a/submodule-config.c b/submodule-config.c index 098085be69b976..33eb62d31aa236 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -251,6 +251,8 @@ static int parse_push_recurse(const char *opt, const char *arg, return RECURSE_SUBMODULES_ON_DEMAND; else if (!strcmp(arg, "check")) return RECURSE_SUBMODULES_CHECK; + else if (!strcmp(arg, "only")) + return RECURSE_SUBMODULES_ONLY; else if (die_on_error) die("bad %s argument: %s", opt, arg); else diff --git a/submodule.h b/submodule.h index 23d76682b1ea12..4a83a4c8dffcb9 100644 --- a/submodule.h +++ b/submodule.h @@ -6,6 +6,7 @@ struct argv_array; struct sha1_array; enum { + RECURSE_SUBMODULES_ONLY = -5, RECURSE_SUBMODULES_CHECK = -4, RECURSE_SUBMODULES_ERROR = -3, RECURSE_SUBMODULES_NONE = -2, From 225e8bf778d21104da10cfb316e0e2898b24e809 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 19 Dec 2016 10:25:33 -0800 Subject: [PATCH 0105/1540] push: add option to push only submodules Teach push the --recurse-submodules=only option. This enables push to recursively push all unpushed submodules while leaving the superproject unpushed. This is a desirable feature in a scenario where updates to the superproject are handled automatically by some other means, perhaps a tool like Gerrit code review. In this scenario, a developer could make a change which spans multiple submodules and then push their commits for code review. Upon completion of the code review, their commits can be accepted and applied to their respective submodules while the code review tool can then automatically update the superproject to the most recent SHA1 of each submodule. This would reduce the merge conflicts in the superproject that could occur if multiple people are contributing to the same submodule. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- builtin/push.c | 2 ++ t/t5531-deep-submodule-push.sh | 21 +++++++++++++++++++++ transport.c | 15 +++++++++++---- transport.h | 1 + 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/builtin/push.c b/builtin/push.c index 3bb9d6b7e63b3e..9433797539c4fe 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -565,6 +565,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) + flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; if (tags) add_refspec("refs/tags/*"); diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 1524ff5ba692d9..f55137f76ffac4 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -454,4 +454,25 @@ test_expect_success 'push --dry-run does not recursively update submodules' ' test_cmp expected_submodule actual_submodule ' +test_expect_success 'push --dry-run does not recursively update submodules' ' + git -C work push --dry-run --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_pub actual_pub && + test_cmp expected_submodule actual_submodule +' + +test_expect_success 'push only unpushed submodules recursively' ' + git -C work/gar/bage rev-parse master >expected_submodule && + git -C pub.git rev-parse master >expected_pub && + + git -C work push --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + test_done diff --git a/transport.c b/transport.c index 04e5d6623e3901..20ebee84c34009 100644 --- a/transport.c +++ b/transport.c @@ -947,7 +947,9 @@ int transport_push(struct transport *transport, if (run_pre_push_hook(transport, remote_refs)) return -1; - if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) { + if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_ONLY)) && + !is_bare_repository()) { struct ref *ref = remote_refs; struct sha1_array commits = SHA1_ARRAY_INIT; @@ -965,7 +967,8 @@ int transport_push(struct transport *transport, } if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) || - ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && + ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_ONLY)) && !pretend)) && !is_bare_repository()) { struct ref *ref = remote_refs; struct string_list needs_pushing = STRING_LIST_INIT_DUP; @@ -984,7 +987,10 @@ int transport_push(struct transport *transport, sha1_array_clear(&commits); } - push_ret = transport->push_refs(transport, remote_refs, flags); + if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY)) + push_ret = transport->push_refs(transport, remote_refs, flags); + else + push_ret = 0; err = push_had_errors(remote_refs); ret = push_ret | err; @@ -996,7 +1002,8 @@ int transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_SET_UPSTREAM) set_upstreams(transport, remote_refs, pretend); - if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { + if (!(flags & (TRANSPORT_PUSH_DRY_RUN | + TRANSPORT_RECURSE_SUBMODULES_ONLY))) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) transport_update_tracking_ref(transport->remote, ref, verbose); diff --git a/transport.h b/transport.h index 1b654583676c62..efd5fb668c678c 100644 --- a/transport.h +++ b/transport.h @@ -146,6 +146,7 @@ struct transport { #define TRANSPORT_PUSH_CERT_IF_ASKED (1<<12) #define TRANSPORT_PUSH_ATOMIC (1<<13) #define TRANSPORT_PUSH_OPTIONS (1<<14) +#define TRANSPORT_RECURSE_SUBMODULES_ONLY (1<<15) extern int transport_summary_width(const struct ref *refs); From bc44f9332a8f6b258d7b7d2ecba063aa7f48e182 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Dec 2016 10:35:54 -0800 Subject: [PATCH 0106/1540] t4201: make tests work with and without the MINGW prerequiste Make sure the tests do not depend on the result of the previous tests. With MINGW prerequisite satisfied, a "reset to original and rebuild" in an earlier test was skipped, resulting in different history being tested with this and the next tests. Signed-off-by: Junio C Hamano --- t/t4201-shortlog.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index 6c7c6374812a76..9df054bf05b8cd 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -191,8 +191,14 @@ test_expect_success 'shortlog with --output=' ' ' test_expect_success 'shortlog --committer (internal)' ' + git checkout --orphan side && + git commit --allow-empty -m one && + git commit --allow-empty -m two && + GIT_COMMITTER_NAME="Sin Nombre" git commit --allow-empty -m three && + cat >expect <<-\EOF && - 3 C O Mitter + 2 C O Mitter + 1 Sin Nombre EOF git shortlog -nsc HEAD >actual && test_cmp expect actual From 405d7f4af64ece9a49c48b1b0c43b161b2dd94d5 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Wed, 21 Dec 2016 06:04:48 +0900 Subject: [PATCH 0107/1540] fast-import: properly fanout notes when tree is imported In typical uses of fast-import, trees are inherited from a parent commit. In that case, the tree_entry for the branch looks like: .versions[1].sha1 = $some_sha1 .tree = However, when trees are imported, rather than inherited, that is not the case. One can import a tree with a filemodify command, replacing the root tree object. e.g. "M 040000 $some_sha1 \n" In this case, the tree_entry for the branch looks like: .versions[1].sha1 = $some_sha1 .tree = NULL When adding new notes with the notemodify command, do_change_note_fanout is called to get a notes count, and to do so, it loops over the tree_entry->tree, but doesn't do anything when the tree is NULL. In the latter case above, it means do_change_note_fanout thinks the tree contains no notes, and new notes are added with no fanout. Interestingly, do_change_note_fanout does check whether subdirectories have a NULL .tree, in which case it uses load_tree(). Which means the right behaviour happens when using the filemodify command to import subdirectories. This change makes do_change_note_fanount call load_tree() whenever the tree_entry it is given has no tree loaded, making all cases handled equally. Signed-off-by: Mike Hommey Reviewed-by: Johan Herland Signed-off-by: Junio C Hamano --- fast-import.c | 8 ++++--- t/t9301-fast-import-notes.sh | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/fast-import.c b/fast-import.c index cb545d7df514b7..5e528b19997158 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2220,13 +2220,17 @@ static uintmax_t do_change_note_fanout( char *fullpath, unsigned int fullpath_len, unsigned char fanout) { - struct tree_content *t = root->tree; + struct tree_content *t; struct tree_entry *e, leaf; unsigned int i, tmp_hex_sha1_len, tmp_fullpath_len; uintmax_t num_notes = 0; unsigned char sha1[20]; char realpath[60]; + if (!root->tree) + load_tree(root); + t = root->tree; + for (i = 0; t && i < t->entry_count; i++) { e = t->entries[i]; tmp_hex_sha1_len = hex_sha1_len + e->name->str_len; @@ -2278,8 +2282,6 @@ static uintmax_t do_change_note_fanout( leaf.tree); } else if (S_ISDIR(e->versions[1].mode)) { /* This is a subdir that may contain note entries */ - if (!e->tree) - load_tree(e); num_notes += do_change_note_fanout(orig_root, e, hex_sha1, tmp_hex_sha1_len, fullpath, tmp_fullpath_len, fanout); diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh index 83acf68bc3c197..dadc70b7d5705d 100755 --- a/t/t9301-fast-import-notes.sh +++ b/t/t9301-fast-import-notes.sh @@ -483,6 +483,48 @@ test_expect_success 'verify that lots of notes trigger a fanout scheme' ' ' +# Create another notes tree from the one above +SP=" " +cat >>input < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data <>expect_non-note1 << EOF This is not a note, but rather a regular file residing in a notes tree EOF From 1d1bdafd64266e5ee3bd46c6965228f32e4022ea Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Dec 2016 14:57:26 -0800 Subject: [PATCH 0108/1540] Third batch for 2.12 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index c8b7fbb700e9dc..159a1bbb83c423 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -35,6 +35,14 @@ UI, Workflows & Features remove the metadata left by an earlier "git rebase" that was manually aborted without using "git rebase --abort". + * "git clone --reference $there --recurse-submodules $super" has been + taught to guess repositories usable as references for submodules of + $super that are embedded in $there while making a clone of the + superproject borrow objects from $there; extend the mechanism to + also allow submodules of these submodules to borrow repositories + embedded in these clones of the submodules embedded in the clone of + the superproject. + Performance, Internal Implementation, Development Support etc. @@ -162,6 +170,38 @@ notes for details). mergetool" has been modernized to match recent versions of xxdiff. (merge 6cf5f6cef7 da/mergetool-xxdiff-hotkey later to maint). + * Unlike "git am --abort", "git cherry-pick --abort" moved HEAD back + to where cherry-pick started while picking multiple changes, when + the cherry-pick stopped to ask for help from the user, and the user + did "git reset --hard" to a different commit in order to re-attempt + the operation. + (merge ce73bb22d8 sb/sequencer-abort-safety later to maint). + + * Code cleanup in shallow boundary computation. + (merge 649b0c316a nd/shallow-fixup later to maint). + + * A recent update to receive-pack to make it easier to drop garbage + objects made it clear that GIT_ALTERNATE_OBJECT_DIRECTORIES cannot + have a pathname with a colon in it (no surprise!), and this in turn + made it impossible to push into a repository at such a path. This + has been fixed by introducing a quoting mechanism used when + appending such a path to the colon-separated list. + (merge 5e74824fac jk/quote-env-path-list-component later to maint). + + * The function usage_msg_opt() has been updated to say "fatal:" + before the custom message programs give, when they want to die + with a message about wrong command line options followed by the + standard usage string. + (merge 87433261a4 jk/parseopt-usage-msg-opt later to maint). + + * "git index-pack --stdin" needs an access to an existing repository, + but "git index-pack file.pack" to generate an .idx file that + corresponds to a packfile does not. + (merge 29401e1575 jk/index-pack-wo-repo-from-stdin later to maint). + + * Fix for NDEBUG builds. + (merge 08414938a2 jt/mailinfo-fold-in-body-headers later to maint). + * Other minor doc, test and build updates and code cleanups. (merge fa6ca11105 nd/qsort-in-merge-recursive later to maint). (merge fa3142c919 ak/lazy-prereq-mktemp later to maint). From e9a379c352b02ca560c58a56aa723994aa42666f Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 21 Dec 2016 22:51:35 +0100 Subject: [PATCH 0109/1540] real_path: canonicalize directory separators in root parts When an absolute path is resolved, resolution begins at the first path component after the root part. The root part is just copied verbatim, because it must not be inspected for symbolic links. For POSIX paths, this is just the initial slash, but on Windows, the root part has the forms c:\ or \\server\share. We do want to canonicalize the back-slashes in the root part because these parts are compared to the result of getcwd(), which does return a fully canonicalized path. Factor out a helper that splits off the root part, and have it canonicalize the copied part. This change was prompted because t1504-ceiling-dirs.sh caught a breakage in GIT_CEILING_DIRECTORIES handling on Windows. Signed-off-by: Johannes Sixt Acked-by: Brandon Williams Signed-off-by: Junio C Hamano --- abspath.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/abspath.c b/abspath.c index 79ee3108678c97..1d56f5ed9f9500 100644 --- a/abspath.c +++ b/abspath.c @@ -48,6 +48,19 @@ static void get_next_component(struct strbuf *next, struct strbuf *remaining) strbuf_remove(remaining, 0, end - remaining->buf); } +/* copies root part from remaining to resolved, canonicalizing it on the way */ +static void get_root_part(struct strbuf *resolved, struct strbuf *remaining) +{ + int offset = offset_1st_component(remaining->buf); + + strbuf_reset(resolved); + strbuf_add(resolved, remaining->buf, offset); +#ifdef GIT_WINDOWS_NATIVE + convert_slashes(resolved->buf); +#endif + strbuf_remove(remaining, 0, offset); +} + /* We allow "recursive" symbolic links. Only within reason, though. */ #define MAXSYMLINKS 5 @@ -80,14 +93,10 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path, goto error_out; } - strbuf_reset(resolved); + strbuf_addstr(&remaining, path); + get_root_part(resolved, &remaining); - if (is_absolute_path(path)) { - /* absolute path; start with only root as being resolved */ - int offset = offset_1st_component(path); - strbuf_add(resolved, path, offset); - strbuf_addstr(&remaining, path + offset); - } else { + if (!resolved->len) { /* relative path; can use CWD as the initial resolved path */ if (strbuf_getcwd(resolved)) { if (die_on_error) @@ -95,7 +104,6 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path, else goto error_out; } - strbuf_addstr(&remaining, path); } /* Iterate over the remaining path components */ @@ -150,10 +158,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path, if (is_absolute_path(symlink.buf)) { /* absolute symlink; set resolved to root */ - int offset = offset_1st_component(symlink.buf); - strbuf_reset(resolved); - strbuf_add(resolved, symlink.buf, offset); - strbuf_remove(&symlink, 0, offset); + get_root_part(resolved, &symlink); } else { /* * relative symlink From 5688c28d81e9103a234efeedcb0568c2c4dd0bfb Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:16 -0800 Subject: [PATCH 0110/1540] submodules: add helper to determine if a submodule is populated Add the `is_submodule_populated()` helper function to submodules.c. `is_submodule_populated()` performes a check to see if a submodule has been checkout out (and has a valid .git directory/file) at the given path. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- submodule.c | 15 +++++++++++++++ submodule.h | 1 + 2 files changed, 16 insertions(+) diff --git a/submodule.c b/submodule.c index c85ba50110606a..ee3198dc245a31 100644 --- a/submodule.c +++ b/submodule.c @@ -198,6 +198,21 @@ void gitmodules_config(void) } } +/* + * Determine if a submodule has been populated at a given 'path' + */ +int is_submodule_populated(const char *path) +{ + int ret = 0; + char *gitdir = xstrfmt("%s/.git", path); + + if (resolve_gitdir(gitdir)) + ret = 1; + + free(gitdir); + return ret; +} + int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst) { diff --git a/submodule.h b/submodule.h index d9e197a948fdab..c4af5059813f59 100644 --- a/submodule.h +++ b/submodule.h @@ -37,6 +37,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); +extern int is_submodule_populated(const char *path); int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); From f9f42560e2911a5eef9a3d463a63cfd48d54dd07 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:17 -0800 Subject: [PATCH 0111/1540] submodules: add helper to determine if a submodule is initialized Add the `is_submodule_initialized()` helper function to submodules.c. `is_submodule_initialized()` performs a check to determine if the submodule at the given path has been initialized. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- submodule.c | 23 +++++++++++++++++++++++ submodule.h | 1 + 2 files changed, 24 insertions(+) diff --git a/submodule.c b/submodule.c index ee3198dc245a31..edffaa186bb408 100644 --- a/submodule.c +++ b/submodule.c @@ -198,6 +198,29 @@ void gitmodules_config(void) } } +/* + * Determine if a submodule has been initialized at a given 'path' + */ +int is_submodule_initialized(const char *path) +{ + int ret = 0; + const struct submodule *module = NULL; + + module = submodule_from_path(null_sha1, path); + + if (module) { + char *key = xstrfmt("submodule.%s.url", module->name); + char *value = NULL; + + ret = !git_config_get_string(key, &value); + + free(value); + free(key); + } + + return ret; +} + /* * Determine if a submodule has been populated at a given 'path' */ diff --git a/submodule.h b/submodule.h index c4af5059813f59..6ec5f2f207c7a0 100644 --- a/submodule.h +++ b/submodule.h @@ -37,6 +37,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); +extern int is_submodule_initialized(const char *path); extern int is_submodule_populated(const char *path); int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); From 9ebf689aad72bfc091da21e1d73a05308f1ace85 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:18 -0800 Subject: [PATCH 0112/1540] submodules: load gitmodules file from commit sha1 teach submodules to load a '.gitmodules' file from a commit sha1. This enables the population of the submodule_cache to be based on the state of the '.gitmodules' file from a particular commit. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- cache.h | 2 ++ config.c | 8 ++++---- submodule-config.c | 6 +++--- submodule-config.h | 3 +++ submodule.c | 12 ++++++++++++ submodule.h | 1 + 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/cache.h b/cache.h index e12a5d9129b938..de237cab6b760c 100644 --- a/cache.h +++ b/cache.h @@ -1693,6 +1693,8 @@ extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type, const char *name, const char *buf, size_t len, void *data); +extern int git_config_from_blob_sha1(config_fn_t fn, const char *name, + const unsigned char *sha1, void *data); extern void git_config_push_parameter(const char *text); extern int git_config_from_parameters(config_fn_t fn, void *data); extern void git_config(config_fn_t fn, void *); diff --git a/config.c b/config.c index 83fdecb1bc9f6f..4d78e7227c4f5d 100644 --- a/config.c +++ b/config.c @@ -1214,10 +1214,10 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ return do_config_from(&top, fn, data); } -static int git_config_from_blob_sha1(config_fn_t fn, - const char *name, - const unsigned char *sha1, - void *data) +int git_config_from_blob_sha1(config_fn_t fn, + const char *name, + const unsigned char *sha1, + void *data) { enum object_type type; char *buf; diff --git a/submodule-config.c b/submodule-config.c index 098085be69b976..8b9a2ef2888c3f 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -379,9 +379,9 @@ static int parse_config(const char *var, const char *value, void *data) return ret; } -static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, - unsigned char *gitmodules_sha1, - struct strbuf *rev) +int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, + unsigned char *gitmodules_sha1, + struct strbuf *rev) { int ret = 0; diff --git a/submodule-config.h b/submodule-config.h index d05c542d2cdace..78584ba6a715f3 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -29,6 +29,9 @@ const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name); const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path); +extern int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, + unsigned char *gitmodules_sha1, + struct strbuf *rev); void submodule_free(void); #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index edffaa186bb408..260090898acf47 100644 --- a/submodule.c +++ b/submodule.c @@ -198,6 +198,18 @@ void gitmodules_config(void) } } +void gitmodules_config_sha1(const unsigned char *commit_sha1) +{ + struct strbuf rev = STRBUF_INIT; + unsigned char sha1[20]; + + if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) { + git_config_from_blob_sha1(submodule_config, rev.buf, + sha1, NULL); + } + strbuf_release(&rev); +} + /* * Determine if a submodule has been initialized at a given 'path' */ diff --git a/submodule.h b/submodule.h index 6ec5f2f207c7a0..9203d89a595a9c 100644 --- a/submodule.h +++ b/submodule.h @@ -37,6 +37,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); +extern void gitmodules_config_sha1(const unsigned char *commit_sha1); extern int is_submodule_initialized(const char *path); extern int is_submodule_populated(const char *path); int parse_submodule_update_strategy(const char *value, From 4538eef564c81c96f2874ccadc54d3c69cc0e19c Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:19 -0800 Subject: [PATCH 0113/1540] grep: add submodules as a grep source type Add `GREP_SOURCE_SUBMODULE` as a grep_source type and cases for this new type in the various switch statements in grep.c. When initializing a grep_source with type `GREP_SOURCE_SUBMODULE` the identifier can either be NULL (to indicate that the working tree will be used) or a SHA1 (the REV of the submodule to be grep'd). If the identifier is a SHA1 then we want to fall through to the `GREP_SOURCE_SHA1` case to handle the copying of the SHA1. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- grep.c | 16 +++++++++++++++- grep.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/grep.c b/grep.c index 1194d35b5d06d7..0dbdc1d0078930 100644 --- a/grep.c +++ b/grep.c @@ -1735,12 +1735,23 @@ void grep_source_init(struct grep_source *gs, enum grep_source_type type, case GREP_SOURCE_FILE: gs->identifier = xstrdup(identifier); break; + case GREP_SOURCE_SUBMODULE: + if (!identifier) { + gs->identifier = NULL; + break; + } + /* + * FALL THROUGH + * If the identifier is non-NULL (in the submodule case) it + * will be a SHA1 that needs to be copied. + */ case GREP_SOURCE_SHA1: gs->identifier = xmalloc(20); hashcpy(gs->identifier, identifier); break; case GREP_SOURCE_BUF: gs->identifier = NULL; + break; } } @@ -1760,6 +1771,7 @@ void grep_source_clear_data(struct grep_source *gs) switch (gs->type) { case GREP_SOURCE_FILE: case GREP_SOURCE_SHA1: + case GREP_SOURCE_SUBMODULE: free(gs->buf); gs->buf = NULL; gs->size = 0; @@ -1831,8 +1843,10 @@ static int grep_source_load(struct grep_source *gs) return grep_source_load_sha1(gs); case GREP_SOURCE_BUF: return gs->buf ? 0 : -1; + case GREP_SOURCE_SUBMODULE: + break; } - die("BUG: invalid grep_source type"); + die("BUG: invalid grep_source type to load"); } void grep_source_load_driver(struct grep_source *gs) diff --git a/grep.h b/grep.h index 5856a23e462077..267534ca24df53 100644 --- a/grep.h +++ b/grep.h @@ -161,6 +161,7 @@ struct grep_source { GREP_SOURCE_SHA1, GREP_SOURCE_FILE, GREP_SOURCE_BUF, + GREP_SOURCE_SUBMODULE, } type; void *identifier; From 0281e487fd913bd9a32a710f3109ff3002f3e4a9 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:20 -0800 Subject: [PATCH 0114/1540] grep: optionally recurse into submodules Allow grep to recognize submodules and recursively search for patterns in each submodule. This is done by forking off a process to recursively call grep on each submodule. The top level --super-prefix option is used to pass a path to the submodule which can in turn be used to prepend to output or in pathspec matching logic. Recursion only occurs for submodules which have been initialized and checked out by the parent project. If a submodule hasn't been initialized and checked out it is simply skipped. In order to support the existing multi-threading infrastructure in grep, output from each child process is captured in a strbuf so that it can be later printed to the console in an ordered fashion. To limit the number of theads that are created, each child process has half the number of threads as its parents (minimum of 1), otherwise we potentailly have a fork-bomb. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- Documentation/git-grep.txt | 5 + builtin/grep.c | 300 +++++++++++++++++++++++++++-- git.c | 2 +- t/t7814-grep-recurse-submodules.sh | 99 ++++++++++ 4 files changed, 386 insertions(+), 20 deletions(-) create mode 100755 t/t7814-grep-recurse-submodules.sh diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 0ecea6e4912f63..17aa1ba702590d 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -26,6 +26,7 @@ SYNOPSIS [--threads ] [-f ] [-e] [--and|--or|--not|(|)|-e ...] + [--recurse-submodules] [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | ...] [--] [...] @@ -88,6 +89,10 @@ OPTIONS mechanism. Only useful when searching files in the current directory with `--no-index`. +--recurse-submodules:: + Recursively search in each submodule that has been initialized and + checked out in the repository. + -a:: --text:: Process binary files as if they were text. diff --git a/builtin/grep.c b/builtin/grep.c index 8887b6addb5f90..dca0be6475cb4e 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -18,12 +18,20 @@ #include "quote.h" #include "dir.h" #include "pathspec.h" +#include "submodule.h" static char const * const grep_usage[] = { N_("git grep [] [-e] [...] [[--] ...]"), NULL }; +static const char *super_prefix; +static int recurse_submodules; +static struct argv_array submodule_options = ARGV_ARRAY_INIT; + +static int grep_submodule_launch(struct grep_opt *opt, + const struct grep_source *gs); + #define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; @@ -174,7 +182,10 @@ static void *run(void *arg) break; opt->output_priv = w; - hit |= grep_source(opt, &w->source); + if (w->source.type == GREP_SOURCE_SUBMODULE) + hit |= grep_submodule_launch(opt, &w->source); + else + hit |= grep_source(opt, &w->source); grep_source_clear_data(&w->source); work_done(w); } @@ -300,6 +311,10 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, if (opt->relative && opt->prefix_length) { quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); strbuf_insert(&pathbuf, 0, filename, tree_name_len); + } else if (super_prefix) { + strbuf_add(&pathbuf, filename, tree_name_len); + strbuf_addstr(&pathbuf, super_prefix); + strbuf_addstr(&pathbuf, filename + tree_name_len); } else { strbuf_addstr(&pathbuf, filename); } @@ -328,10 +343,13 @@ static int grep_file(struct grep_opt *opt, const char *filename) { struct strbuf buf = STRBUF_INIT; - if (opt->relative && opt->prefix_length) + if (opt->relative && opt->prefix_length) { quote_path_relative(filename, opt->prefix, &buf); - else + } else { + if (super_prefix) + strbuf_addstr(&buf, super_prefix); strbuf_addstr(&buf, filename); + } #ifndef NO_PTHREADS if (num_threads) { @@ -378,31 +396,260 @@ static void run_pager(struct grep_opt *opt, const char *prefix) exit(status); } -static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) +static void compile_submodule_options(const struct grep_opt *opt, + const struct pathspec *pathspec, + int cached, int untracked, + int opt_exclude, int use_index, + int pattern_type_arg) +{ + struct grep_pat *pattern; + int i; + + if (recurse_submodules) + argv_array_push(&submodule_options, "--recurse-submodules"); + + if (cached) + argv_array_push(&submodule_options, "--cached"); + if (!use_index) + argv_array_push(&submodule_options, "--no-index"); + if (untracked) + argv_array_push(&submodule_options, "--untracked"); + if (opt_exclude > 0) + argv_array_push(&submodule_options, "--exclude-standard"); + + if (opt->invert) + argv_array_push(&submodule_options, "-v"); + if (opt->ignore_case) + argv_array_push(&submodule_options, "-i"); + if (opt->word_regexp) + argv_array_push(&submodule_options, "-w"); + switch (opt->binary) { + case GREP_BINARY_NOMATCH: + argv_array_push(&submodule_options, "-I"); + break; + case GREP_BINARY_TEXT: + argv_array_push(&submodule_options, "-a"); + break; + default: + break; + } + if (opt->allow_textconv) + argv_array_push(&submodule_options, "--textconv"); + if (opt->max_depth != -1) + argv_array_pushf(&submodule_options, "--max-depth=%d", + opt->max_depth); + if (opt->linenum) + argv_array_push(&submodule_options, "-n"); + if (!opt->pathname) + argv_array_push(&submodule_options, "-h"); + if (!opt->relative) + argv_array_push(&submodule_options, "--full-name"); + if (opt->name_only) + argv_array_push(&submodule_options, "-l"); + if (opt->unmatch_name_only) + argv_array_push(&submodule_options, "-L"); + if (opt->null_following_name) + argv_array_push(&submodule_options, "-z"); + if (opt->count) + argv_array_push(&submodule_options, "-c"); + if (opt->file_break) + argv_array_push(&submodule_options, "--break"); + if (opt->heading) + argv_array_push(&submodule_options, "--heading"); + if (opt->pre_context) + argv_array_pushf(&submodule_options, "--before-context=%d", + opt->pre_context); + if (opt->post_context) + argv_array_pushf(&submodule_options, "--after-context=%d", + opt->post_context); + if (opt->funcname) + argv_array_push(&submodule_options, "-p"); + if (opt->funcbody) + argv_array_push(&submodule_options, "-W"); + if (opt->all_match) + argv_array_push(&submodule_options, "--all-match"); + if (opt->debug) + argv_array_push(&submodule_options, "--debug"); + if (opt->status_only) + argv_array_push(&submodule_options, "-q"); + + switch (pattern_type_arg) { + case GREP_PATTERN_TYPE_BRE: + argv_array_push(&submodule_options, "-G"); + break; + case GREP_PATTERN_TYPE_ERE: + argv_array_push(&submodule_options, "-E"); + break; + case GREP_PATTERN_TYPE_FIXED: + argv_array_push(&submodule_options, "-F"); + break; + case GREP_PATTERN_TYPE_PCRE: + argv_array_push(&submodule_options, "-P"); + break; + case GREP_PATTERN_TYPE_UNSPECIFIED: + break; + } + + for (pattern = opt->pattern_list; pattern != NULL; + pattern = pattern->next) { + switch (pattern->token) { + case GREP_PATTERN: + argv_array_pushf(&submodule_options, "-e%s", + pattern->pattern); + break; + case GREP_AND: + case GREP_OPEN_PAREN: + case GREP_CLOSE_PAREN: + case GREP_NOT: + case GREP_OR: + argv_array_push(&submodule_options, pattern->pattern); + break; + /* BODY and HEAD are not used by git-grep */ + case GREP_PATTERN_BODY: + case GREP_PATTERN_HEAD: + break; + } + } + + /* + * Limit number of threads for child process to use. + * This is to prevent potential fork-bomb behavior of git-grep as each + * submodule process has its own thread pool. + */ + argv_array_pushf(&submodule_options, "--threads=%d", + (num_threads + 1) / 2); + + /* Add Pathspecs */ + argv_array_push(&submodule_options, "--"); + for (i = 0; i < pathspec->nr; i++) + argv_array_push(&submodule_options, + pathspec->items[i].original); +} + +/* + * Launch child process to grep contents of a submodule + */ +static int grep_submodule_launch(struct grep_opt *opt, + const struct grep_source *gs) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status, i; + struct work_item *w = opt->output_priv; + + prepare_submodule_repo_env(&cp.env_array); + + /* Add super prefix */ + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + super_prefix ? super_prefix : "", + gs->name); + argv_array_push(&cp.args, "grep"); + + /* Add options */ + for (i = 0; i < submodule_options.argc; i++) + argv_array_push(&cp.args, submodule_options.argv[i]); + + cp.git_cmd = 1; + cp.dir = gs->path; + + /* + * Capture output to output buffer and check the return code from the + * child process. A '0' indicates a hit, a '1' indicates no hit and + * anything else is an error. + */ + status = capture_command(&cp, &w->out, 0); + if (status && (status != 1)) { + /* flush the buffer */ + write_or_die(1, w->out.buf, w->out.len); + die("process for submodule '%s' failed with exit code: %d", + gs->name, status); + } + + /* invert the return code to make a hit equal to 1 */ + return !status; +} + +/* + * Prep grep structures for a submodule grep + * sha1: the sha1 of the submodule or NULL if using the working tree + * filename: name of the submodule including tree name of parent + * path: location of the submodule + */ +static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, + const char *filename, const char *path) +{ + if (!is_submodule_initialized(path)) + return 0; + if (!is_submodule_populated(path)) + return 0; + +#ifndef NO_PTHREADS + if (num_threads) { + add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1); + return 0; + } else +#endif + { + struct work_item w; + int hit; + + grep_source_init(&w.source, GREP_SOURCE_SUBMODULE, + filename, path, sha1); + strbuf_init(&w.out, 0); + opt->output_priv = &w; + hit = grep_submodule_launch(opt, &w.source); + + write_or_die(1, w.out.buf, w.out.len); + + grep_source_clear(&w.source); + strbuf_release(&w.out); + return hit; + } +} + +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, + int cached) { int hit = 0; int nr; + struct strbuf name = STRBUF_INIT; + int name_base_len = 0; + if (super_prefix) { + name_base_len = strlen(super_prefix); + strbuf_addstr(&name, super_prefix); + } + read_cache(); for (nr = 0; nr < active_nr; nr++) { const struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!ce_path_match(ce, pathspec, NULL)) + strbuf_setlen(&name, name_base_len); + strbuf_addstr(&name, ce->name); + + if (S_ISREG(ce->ce_mode) && + match_pathspec(pathspec, name.buf, name.len, 0, NULL, + S_ISDIR(ce->ce_mode) || + S_ISGITLINK(ce->ce_mode))) { + /* + * If CE_VALID is on, we assume worktree file and its + * cache entry are identical, even if worktree file has + * been modified, so use cache version instead + */ + if (cached || (ce->ce_flags & CE_VALID) || + ce_skip_worktree(ce)) { + if (ce_stage(ce) || ce_intent_to_add(ce)) + continue; + hit |= grep_sha1(opt, ce->oid.hash, ce->name, + 0, ce->name); + } else { + hit |= grep_file(opt, ce->name); + } + } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && + submodule_path_match(pathspec, name.buf, NULL)) { + hit |= grep_submodule(opt, NULL, ce->name, ce->name); + } else { continue; - /* - * If CE_VALID is on, we assume worktree file and its cache entry - * are identical, even if worktree file has been modified, so use - * cache version instead - */ - if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { - if (ce_stage(ce) || ce_intent_to_add(ce)) - continue; - hit |= grep_sha1(opt, ce->oid.hash, ce->name, 0, - ce->name); } - else - hit |= grep_file(opt, ce->name); + if (ce_stage(ce)) { do { nr++; @@ -413,6 +660,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int if (hit && opt->status_only) break; } + + strbuf_release(&name); return hit; } @@ -651,6 +900,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, N_("ignore files specified via '.gitignore'"), 1), + OPT_BOOL(0, "recurse-submodules", &recurse_submodules, + N_("recursivley search in each submodule")), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -755,6 +1006,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) init_grep_defaults(); git_config(grep_cmd_config, NULL); grep_init(&opt, prefix); + super_prefix = get_super_prefix(); /* * If there is no -- then the paths must exist in the working @@ -872,6 +1124,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) pathspec.max_depth = opt.max_depth; pathspec.recursive = 1; + if (recurse_submodules) { + gitmodules_config(); + compile_submodule_options(&opt, &pathspec, cached, untracked, + opt_exclude, use_index, + pattern_type_arg); + } + if (show_in_pager && (cached || list.nr)) die(_("--open-files-in-pager only works on the worktree")); @@ -895,6 +1154,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } + if (recurse_submodules && (!use_index || untracked || list.nr)) + die(_("option not supported with --recurse-submodules.")); + if (!show_in_pager && !opt.status_only) setup_pager(); diff --git a/git.c b/git.c index dce529fcbfd6e5..c95d3e30da8951 100644 --- a/git.c +++ b/git.c @@ -434,7 +434,7 @@ static struct cmd_struct commands[] = { { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP_GENTLY }, + { "grep", cmd_grep, RUN_SETUP_GENTLY | SUPPORT_SUPER_PREFIX }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY }, diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh new file mode 100755 index 00000000000000..1019125e52b247 --- /dev/null +++ b/t/t7814-grep-recurse-submodules.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +test_description='Test grep recurse-submodules feature + +This test verifies the recurse-submodules feature correctly greps across +submodules. +' + +. ./test-lib.sh + +test_expect_success 'setup directory structure and submodule' ' + echo "foobar" >a && + mkdir b && + echo "bar" >b/b && + git add a b && + git commit -m "add a and b" && + git init submodule && + echo "foobar" >submodule/a && + git -C submodule add a && + git -C submodule commit -m "add a" && + git submodule add ./submodule && + git commit -m "added submodule" +' + +test_expect_success 'grep correctly finds patterns in a submodule' ' + cat >expect <<-\EOF && + a:foobar + b/b:bar + submodule/a:foobar + EOF + + git grep -e "bar" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and basic pathspecs' ' + cat >expect <<-\EOF && + submodule/a:foobar + EOF + + git grep -e. --recurse-submodules -- submodule >actual && + test_cmp expect actual +' + +test_expect_success 'grep and nested submodules' ' + git init submodule/sub && + echo "foobar" >submodule/sub/a && + git -C submodule/sub add a && + git -C submodule/sub commit -m "add a" && + git -C submodule submodule add ./sub && + git -C submodule add sub && + git -C submodule commit -m "added sub" && + git add submodule && + git commit -m "updated submodule" && + + cat >expect <<-\EOF && + a:foobar + b/b:bar + submodule/a:foobar + submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and multiple patterns' ' + cat >expect <<-\EOF && + a:foobar + submodule/a:foobar + submodule/sub/a:foobar + EOF + + git grep -e "bar" --and -e "foo" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and multiple patterns' ' + cat >expect <<-\EOF && + b/b:bar + EOF + + git grep -e "bar" --and --not -e "foo" --recurse-submodules >actual && + test_cmp expect actual +' + +test_incompatible_with_recurse_submodules () +{ + test_expect_success "--recurse-submodules and $1 are incompatible" " + test_must_fail git grep -e. --recurse-submodules $1 2>actual && + test_i18ngrep 'not supported with --recurse-submodules' actual + " +} + +test_incompatible_with_recurse_submodules --untracked +test_incompatible_with_recurse_submodules --no-index +test_incompatible_with_recurse_submodules HEAD + +test_done From 74ed43711fd1cd7ce155d338f87ebe52cb74d9e2 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:21 -0800 Subject: [PATCH 0115/1540] grep: enable recurse-submodules to work on objects Teach grep to recursively search in submodules when provided with a object. This allows grep to search a submodule based on the state of the submodule that is present in a commit of the super project. When grep is provided with a object, the name of the object is prefixed to all output. In order to provide uniformity of output between the parent and child processes the option `--parent-basename` has been added so that the child can preface all of it's output with the name of the parent's object instead of the name of the commit SHA1 of the submodule. This changes output from the command `git grep -e. -l --recurse-submodules HEAD` from: HEAD:file :sub/file to: HEAD:file HEAD:sub/file Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- Documentation/git-grep.txt | 13 +++- builtin/grep.c | 76 +++++++++++++++++++-- t/t7814-grep-recurse-submodules.sh | 103 ++++++++++++++++++++++++++++- tree-walk.c | 28 ++++++++ 4 files changed, 211 insertions(+), 9 deletions(-) diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 17aa1ba702590d..71f32f35089241 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -26,7 +26,7 @@ SYNOPSIS [--threads ] [-f ] [-e] [--and|--or|--not|(|)|-e ...] - [--recurse-submodules] + [--recurse-submodules] [--parent-basename ] [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | ...] [--] [...] @@ -91,7 +91,16 @@ OPTIONS --recurse-submodules:: Recursively search in each submodule that has been initialized and - checked out in the repository. + checked out in the repository. When used in combination with the + option the prefix of all submodule output will be the name of + the parent project's object. + +--parent-basename :: + For internal use only. In order to produce uniform output with the + --recurse-submodules option, this option can be used to provide the + basename of a parent's object to a submodule so the submodule + can prefix its output with the parent's name rather than the SHA1 of + the submodule. -a:: --text:: diff --git a/builtin/grep.c b/builtin/grep.c index dca0be6475cb4e..5918a264902fb5 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -19,6 +19,7 @@ #include "dir.h" #include "pathspec.h" #include "submodule.h" +#include "submodule-config.h" static char const * const grep_usage[] = { N_("git grep [] [-e] [...] [[--] ...]"), @@ -28,6 +29,7 @@ static char const * const grep_usage[] = { static const char *super_prefix; static int recurse_submodules; static struct argv_array submodule_options = ARGV_ARRAY_INIT; +static const char *parent_basename; static int grep_submodule_launch(struct grep_opt *opt, const struct grep_source *gs); @@ -534,19 +536,53 @@ static int grep_submodule_launch(struct grep_opt *opt, { struct child_process cp = CHILD_PROCESS_INIT; int status, i; + const char *end_of_base; + const char *name; struct work_item *w = opt->output_priv; + end_of_base = strchr(gs->name, ':'); + if (gs->identifier && end_of_base) + name = end_of_base + 1; + else + name = gs->name; + prepare_submodule_repo_env(&cp.env_array); /* Add super prefix */ argv_array_pushf(&cp.args, "--super-prefix=%s%s/", super_prefix ? super_prefix : "", - gs->name); + name); argv_array_push(&cp.args, "grep"); + /* + * Add basename of parent project + * When performing grep on a tree object the filename is prefixed + * with the object's name: 'tree-name:filename'. In order to + * provide uniformity of output we want to pass the name of the + * parent project's object name to the submodule so the submodule can + * prefix its output with the parent's name and not its own SHA1. + */ + if (gs->identifier && end_of_base) + argv_array_pushf(&cp.args, "--parent-basename=%.*s", + (int) (end_of_base - gs->name), + gs->name); + /* Add options */ - for (i = 0; i < submodule_options.argc; i++) + for (i = 0; i < submodule_options.argc; i++) { + /* + * If there is a tree identifier for the submodule, add the + * rev after adding the submodule options but before the + * pathspecs. To do this we listen for the '--' and insert the + * sha1 before pushing the '--' onto the child process argv + * array. + */ + if (gs->identifier && + !strcmp("--", submodule_options.argv[i])) { + argv_array_push(&cp.args, sha1_to_hex(gs->identifier)); + } + argv_array_push(&cp.args, submodule_options.argv[i]); + } cp.git_cmd = 1; cp.dir = gs->path; @@ -673,12 +709,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, enum interesting match = entry_not_interesting; struct name_entry entry; int old_baselen = base->len; + struct strbuf name = STRBUF_INIT; + int name_base_len = 0; + if (super_prefix) { + strbuf_addstr(&name, super_prefix); + name_base_len = name.len; + } while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(&entry); if (match != all_entries_interesting) { - match = tree_entry_interesting(&entry, base, tn_len, pathspec); + strbuf_addstr(&name, base->buf + tn_len); + match = tree_entry_interesting(&entry, &name, + 0, pathspec); + strbuf_setlen(&name, name_base_len); + if (match == all_entries_not_interesting) break; if (match == entry_not_interesting) @@ -690,8 +736,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, if (S_ISREG(entry.mode)) { hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); - } - else if (S_ISDIR(entry.mode)) { + } else if (S_ISDIR(entry.mode)) { enum object_type type; struct tree_desc sub; void *data; @@ -707,12 +752,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, hit |= grep_tree(opt, pathspec, &sub, base, tn_len, check_attr); free(data); + } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { + hit |= grep_submodule(opt, entry.oid->hash, base->buf, + base->buf + tn_len); } + strbuf_setlen(base, old_baselen); if (hit && opt->status_only) break; } + + strbuf_release(&name); return hit; } @@ -736,6 +787,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); + /* Use parent's name as base when recursing submodules */ + if (recurse_submodules && parent_basename) + name = parent_basename; + len = name ? strlen(name) : 0; strbuf_init(&base, PATH_MAX + len + 1); if (len) { @@ -762,6 +817,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; real_obj = deref_tag(list->objects[i].item, NULL, 0); + + /* load the gitmodules file for this rev */ + if (recurse_submodules) { + submodule_free(); + gitmodules_config_sha1(real_obj->oid.hash); + } if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { hit = 1; if (opt->status_only) @@ -902,6 +963,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("ignore files specified via '.gitignore'"), 1), OPT_BOOL(0, "recurse-submodules", &recurse_submodules, N_("recursivley search in each submodule")), + OPT_STRING(0, "parent-basename", &parent_basename, + N_("basename"), + N_("prepend parent project's basename to output")), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -1154,7 +1218,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } - if (recurse_submodules && (!use_index || untracked || list.nr)) + if (recurse_submodules && (!use_index || untracked)) die(_("option not supported with --recurse-submodules.")); if (!show_in_pager && !opt.status_only) diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 1019125e52b247..d5fc31638e81ef 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -84,6 +84,108 @@ test_expect_success 'grep and multiple patterns' ' test_cmp expect actual ' +test_expect_success 'basic grep tree' ' + cat >expect <<-\EOF && + HEAD:a:foobar + HEAD:b/b:bar + HEAD:submodule/a:foobar + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree HEAD^' ' + cat >expect <<-\EOF && + HEAD^:a:foobar + HEAD^:b/b:bar + HEAD^:submodule/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree HEAD^^' ' + cat >expect <<-\EOF && + HEAD^^:a:foobar + HEAD^^:b/b:bar + EOF + + git grep -e "bar" --recurse-submodules HEAD^^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:foobar + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- submodule >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:foobar + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- "submodule*a" >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and more pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- "submodul?/a" >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and more pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- "submodul*/sub/a" >actual && + test_cmp expect actual +' + +test_expect_success !MINGW 'grep recurse submodule colon in name' ' + git init parent && + test_when_finished "rm -rf parent" && + echo "foobar" >"parent/fi:le" && + git -C parent add "fi:le" && + git -C parent commit -m "add fi:le" && + + git init "su:b" && + test_when_finished "rm -rf su:b" && + echo "foobar" >"su:b/fi:le" && + git -C "su:b" add "fi:le" && + git -C "su:b" commit -m "add fi:le" && + + git -C parent submodule add "../su:b" "su:b" && + git -C parent commit -m "add submodule" && + + cat >expect <<-\EOF && + fi:le:foobar + su:b/fi:le:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules >actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + HEAD:fi:le:foobar + HEAD:su:b/fi:le:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules HEAD >actual && + test_cmp expect actual +' + test_incompatible_with_recurse_submodules () { test_expect_success "--recurse-submodules and $1 are incompatible" " @@ -94,6 +196,5 @@ test_incompatible_with_recurse_submodules () test_incompatible_with_recurse_submodules --untracked test_incompatible_with_recurse_submodules --no-index -test_incompatible_with_recurse_submodules HEAD test_done diff --git a/tree-walk.c b/tree-walk.c index 828f4356be039b..ff776056806dd4 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -1004,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry, */ if (ps->recursive && S_ISDIR(entry->mode)) return entry_interesting; + + /* + * When matching against submodules with + * wildcard characters, ensure that the entry + * at least matches up to the first wild + * character. More accurate matching can then + * be performed in the submodule itself. + */ + if (ps->recursive && S_ISGITLINK(entry->mode) && + !ps_strncmp(item, match + baselen, + entry->path, + item->nowildcard_len - baselen)) + return entry_interesting; } continue; @@ -1040,6 +1053,21 @@ static enum interesting do_match(const struct name_entry *entry, strbuf_setlen(base, base_offset + baselen); return entry_interesting; } + + /* + * When matching against submodules with + * wildcard characters, ensure that the entry + * at least matches up to the first wild + * character. More accurate matching can then + * be performed in the submodule itself. + */ + if (ps->recursive && S_ISGITLINK(entry->mode) && + !ps_strncmp(item, match, base->buf + base_offset, + item->nowildcard_len)) { + strbuf_setlen(base, base_offset + baselen); + return entry_interesting; + } + strbuf_setlen(base, base_offset + baselen); /* From e6fac7f3d3e313a93fe9b1243917669267b33153 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 16 Dec 2016 11:03:22 -0800 Subject: [PATCH 0116/1540] grep: search history of moved submodules If a submodule was renamed at any point since it's inception then if you were to try and grep on a commit prior to the submodule being moved, you wouldn't be able to find a working directory for the submodule since the path in the past is different from the current path. This patch teaches grep to find the .git directory for a submodule in the parents .git/modules/ directory in the event the path to the submodule in the commit that is being searched differs from the state of the currently checked out commit. If found, the child process that is spawned to grep the submodule will chdir into its gitdir instead of a working directory. In order to override the explicit setting of submodule child process's gitdir environment variable (which was introduced in '10f5c526') `GIT_DIR_ENVIORMENT` needs to be pushed onto child process's env_array. This allows the searching of history from a submodule's gitdir, rather than from a working directory. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- builtin/grep.c | 20 +++++++++++++-- t/t7814-grep-recurse-submodules.sh | 41 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/builtin/grep.c b/builtin/grep.c index 5918a264902fb5..2c727ef499c0fa 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -547,6 +547,7 @@ static int grep_submodule_launch(struct grep_opt *opt, name = gs->name; prepare_submodule_repo_env(&cp.env_array); + argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT); /* Add super prefix */ argv_array_pushf(&cp.args, "--super-prefix=%s%s/", @@ -615,8 +616,23 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, { if (!is_submodule_initialized(path)) return 0; - if (!is_submodule_populated(path)) - return 0; + if (!is_submodule_populated(path)) { + /* + * If searching history, check for the presense of the + * submodule's gitdir before skipping the submodule. + */ + if (sha1) { + const struct submodule *sub = + submodule_from_path(null_sha1, path); + if (sub) + path = git_path("modules/%s", sub->name); + + if (!(is_directory(path) && is_git_directory(path))) + return 0; + } else { + return 0; + } + } #ifndef NO_PTHREADS if (num_threads) { diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index d5fc31638e81ef..67247a01d6dd2b 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -186,6 +186,47 @@ test_expect_success !MINGW 'grep recurse submodule colon in name' ' test_cmp expect actual ' +test_expect_success 'grep history with moved submoules' ' + git init parent && + test_when_finished "rm -rf parent" && + echo "foobar" >parent/file && + git -C parent add file && + git -C parent commit -m "add file" && + + git init sub && + test_when_finished "rm -rf sub" && + echo "foobar" >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + + git -C parent submodule add ../sub dir/sub && + git -C parent commit -m "add submodule" && + + cat >expect <<-\EOF && + dir/sub/file:foobar + file:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules >actual && + test_cmp expect actual && + + git -C parent mv dir/sub sub-moved && + git -C parent commit -m "moved submodule" && + + cat >expect <<-\EOF && + file:foobar + sub-moved/file:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules >actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + HEAD^:dir/sub/file:foobar + HEAD^:file:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules HEAD^ >actual && + test_cmp expect actual +' + test_incompatible_with_recurse_submodules () { test_expect_success "--recurse-submodules and $1 are incompatible" " From c06fa62dfc9c882f60250e60ad91fbb6a7e6f8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Tue, 20 Dec 2016 16:48:36 +0700 Subject: [PATCH 0117/1540] config.c: handle lock file in error case in git_config_rename_... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We could rely on atexit() to clean up everything, but let's be explicit when we can. And it's good anyway because the function is called the second time in the same process, we're in trouble. This function should not affect the successful case because after commit_lock_file() is called, rollback_lock_file() becomes no-op, as long as it is initialized. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- config.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index 8065296a116e64..398ffec29d6a47 100644 --- a/config.c +++ b/config.c @@ -2314,7 +2314,7 @@ int git_config_rename_section_in_file(const char *config_filename, if (new_name && !section_name_is_ok(new_name)) { ret = error("invalid section name: %s", new_name); - goto out; + goto out_no_rollback; } if (!config_filename) @@ -2396,6 +2396,8 @@ int git_config_rename_section_in_file(const char *config_filename, ret = error_errno("could not write config file %s", config_filename); out: + rollback_lock_file(lock); +out_no_rollback: free(filename_buf); return ret; } From 48d5014dd42cc4a4465162c9807eaa253715e105 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 1 Nov 2016 18:34:07 -0700 Subject: [PATCH 0118/1540] config.abbrev: document the new default that auto-scales We somehow forgot to update the "default is 7" in the documentation. Also give a way to explicitly ask the auto-scaling by setting config.abbrev to "auto". Signed-off-by: Junio C Hamano --- Documentation/config.txt | 9 +++++---- config.c | 14 ++++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index a0ab66aae70db9..b02f8a402516e0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -783,10 +783,11 @@ core.sparseCheckout:: linkgit:git-read-tree[1] for more information. core.abbrev:: - Set the length object names are abbreviated to. If unspecified, - many commands abbreviate to 7 hexdigits, which may not be enough - for abbreviated object names to stay unique for sufficiently long - time. + Set the length object names are abbreviated to. If + unspecified or set to "auto", an appropriate value is + computed based on the approximate number of packed objects + in your repository, which hopefully is enough for + abbreviated object names to stay unique for some time. add.ignoreErrors:: add.ignore-errors (deprecated):: diff --git a/config.c b/config.c index 83fdecb1bc9f6f..1c571204ebcca4 100644 --- a/config.c +++ b/config.c @@ -834,10 +834,16 @@ static int git_default_core_config(const char *var, const char *value) } if (!strcmp(var, "core.abbrev")) { - int abbrev = git_config_int(var, value); - if (abbrev < minimum_abbrev || abbrev > 40) - return -1; - default_abbrev = abbrev; + if (!value) + return config_error_nonbool(var); + if (!strcasecmp(value, "auto")) + default_abbrev = -1; + else { + int abbrev = git_config_int(var, value); + if (abbrev < minimum_abbrev || abbrev > 40) + return error("abbrev length out of range: %d", abbrev); + default_abbrev = abbrev; + } return 0; } From 22af6fef9b6538c9e87e147a920be9509acf1ddd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Dec 2016 01:14:01 +0000 Subject: [PATCH 0119/1540] git-svn: escape backslashes in refnames This brings git-svn refname escaping up-to-date with commit a4c2e69936df8dd0b071b85664c6cc6a4870dd84 ("Disallow '\' in ref names") from May 2009. Reported-by: Michael Fladischer Message-ID: Signed-off-by: Eric Wong --- perl/Git/SVN.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm index 711d2687a30050..98518f4ddb4c03 100644 --- a/perl/Git/SVN.pm +++ b/perl/Git/SVN.pm @@ -490,7 +490,7 @@ sub refname { # # Additionally, % must be escaped because it is used for escaping # and we want our escaped refname to be reversible - $refname =~ s{([ \%~\^:\?\*\[\t])}{sprintf('%%%02X',ord($1))}eg; + $refname =~ s{([ \%~\^:\?\*\[\t\\])}{sprintf('%%%02X',ord($1))}eg; # no slash-separated component can begin with a dot . # /.* becomes /%2E* From 3cde4e02ee891bff53bac7f6a7d977f50418a4b5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Dec 2016 12:32:22 -0800 Subject: [PATCH 0120/1540] diff: retire "compaction" heuristics When a patch inserts a block of lines, whose last lines are the same as the existing lines that appear before the inserted block, "git diff" can choose any place between these existing lines as the boundary between the pre-context and the added lines (adjusting the end of the inserted block as appropriate) to come up with variants of the same patch, and some variants are easier to read than others. We have been trying to improve the choice of this boundary, and Git 2.11 shipped with an experimental "compaction-heuristic". Since then another attempt to improve the logic further resulted in a new "indent-heuristic" logic. It is agreed that the latter gives better result overall, and the former outlived its usefulness. Retire "compaction", and keep "indent" as an experimental feature. The latter hopefully will be turned on by default in a future release, but that should be done as a separate step. Suggested-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/diff-config.txt | 6 ++--- Documentation/diff-heuristic-options.txt | 2 -- builtin/blame.c | 5 ++-- diff.c | 23 +++-------------- git-add--interactive.perl | 3 --- xdiff/xdiff.h | 3 +-- xdiff/xdiffi.c | 33 ------------------------ 7 files changed, 8 insertions(+), 67 deletions(-) diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index 58f4bd6afa1224..d8570f2a75096c 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -172,10 +172,8 @@ diff.tool:: include::mergetools-diff.txt[] diff.indentHeuristic:: -diff.compactionHeuristic:: - Set one of these options to `true` to enable one of two - experimental heuristics that shift diff hunk boundaries to - make patches easier to read. + Set this option to `true` to enable experimental heuristics + that shift diff hunk boundaries to make patches easier to read. diff.algorithm:: Choose a diff algorithm. The variants are as follows: diff --git a/Documentation/diff-heuristic-options.txt b/Documentation/diff-heuristic-options.txt index 36cb549df97cef..d4f3d9550555cd 100644 --- a/Documentation/diff-heuristic-options.txt +++ b/Documentation/diff-heuristic-options.txt @@ -1,7 +1,5 @@ --indent-heuristic:: --no-indent-heuristic:: ---compaction-heuristic:: ---no-compaction-heuristic:: These are to help debugging and tuning experimental heuristics (which are off by default) that shift diff hunk boundaries to make patches easier to read. diff --git a/builtin/blame.c b/builtin/blame.c index 4ddfadb71f7ef9..ab54a5c1f48ffc 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2596,8 +2596,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) * and are only included here to get included in the "-h" * output: */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental indent-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, - { OPTION_LOWLEVEL_CALLBACK, 0, "compaction-heuristic", NULL, NULL, N_("Use an experimental blank-line-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from instead of calling git-rev-list")), @@ -2645,7 +2644,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } parse_done: no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); - xdl_opts |= revs.diffopt.xdl_opts & (XDF_COMPACTION_HEURISTIC | XDF_INDENT_HEURISTIC); + xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); diff --git a/diff.c b/diff.c index 8981477c436dd1..741ce8c68dc7f4 100644 --- a/diff.c +++ b/diff.c @@ -28,7 +28,6 @@ static int diff_detect_rename_default; static int diff_indent_heuristic; /* experimental */ -static int diff_compaction_heuristic; /* experimental */ static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; @@ -223,16 +222,8 @@ void init_diff_ui_defaults(void) int git_diff_heuristic_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "diff.indentheuristic")) { + if (!strcmp(var, "diff.indentheuristic")) diff_indent_heuristic = git_config_bool(var, value); - if (diff_indent_heuristic) - diff_compaction_heuristic = 0; - } - if (!strcmp(var, "diff.compactionheuristic")) { - diff_compaction_heuristic = git_config_bool(var, value); - if (diff_compaction_heuristic) - diff_indent_heuristic = 0; - } return 0; } @@ -3380,8 +3371,6 @@ void diff_setup(struct diff_options *options) options->xdl_opts |= diff_algorithm; if (diff_indent_heuristic) DIFF_XDL_SET(options, INDENT_HEURISTIC); - else if (diff_compaction_heuristic) - DIFF_XDL_SET(options, COMPACTION_HEURISTIC); options->orderfile = diff_order_file_cfg; @@ -3876,16 +3865,10 @@ int diff_opt_parse(struct diff_options *options, DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); else if (!strcmp(arg, "--ignore-blank-lines")) DIFF_XDL_SET(options, IGNORE_BLANK_LINES); - else if (!strcmp(arg, "--indent-heuristic")) { + else if (!strcmp(arg, "--indent-heuristic")) DIFF_XDL_SET(options, INDENT_HEURISTIC); - DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); - } else if (!strcmp(arg, "--no-indent-heuristic")) - DIFF_XDL_CLR(options, INDENT_HEURISTIC); - else if (!strcmp(arg, "--compaction-heuristic")) { - DIFF_XDL_SET(options, COMPACTION_HEURISTIC); + else if (!strcmp(arg, "--no-indent-heuristic")) DIFF_XDL_CLR(options, INDENT_HEURISTIC); - } else if (!strcmp(arg, "--no-compaction-heuristic")) - DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); else if (!strcmp(arg, "--patience")) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index ee3d812695fa23..5a55d80b9dd848 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -46,7 +46,6 @@ my $diff_algorithm = $repo->config('diff.algorithm'); my $diff_indent_heuristic = $repo->config_bool('diff.indentheuristic'); -my $diff_compaction_heuristic = $repo->config_bool('diff.compactionheuristic'); my $diff_filter = $repo->config('interactive.difffilter'); my $use_readkey = 0; @@ -753,8 +752,6 @@ sub parse_diff { } if ($diff_indent_heuristic) { splice @diff_cmd, 1, 0, "--indent-heuristic"; - } elsif ($diff_compaction_heuristic) { - splice @diff_cmd, 1, 0, "--compaction-heuristic"; } if (defined $patch_mode_revision) { push @diff_cmd, get_diff_reference($patch_mode_revision); diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 8db16d4ae6def5..b090ad8eacfe6e 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -41,8 +41,7 @@ extern "C" { #define XDF_IGNORE_BLANK_LINES (1 << 7) -#define XDF_COMPACTION_HEURISTIC (1 << 8) -#define XDF_INDENT_HEURISTIC (1 << 9) +#define XDF_INDENT_HEURISTIC (1 << 8) #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_FUNCCONTEXT (1 << 2) diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 760fbb6db7583f..93a65680a18284 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -400,11 +400,6 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int is_blank_line(xrecord_t *rec, long flags) -{ - return xdl_blankline(rec->ptr, rec->size, flags); -} - static int recs_match(xrecord_t *rec1, xrecord_t *rec2, long flags) { return (rec1->ha == rec2->ha && @@ -821,7 +816,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { struct xdlgroup g, go; long earliest_end, end_matching_other; long groupsize; - unsigned int blank_lines; group_init(xdf, &g); group_init(xdfo, &go); @@ -846,13 +840,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { */ end_matching_other = -1; - /* - * Boolean value that records whether there are any blank - * lines that could be made to be the last line of this - * group. - */ - blank_lines = 0; - /* Shift the group backward as much as possible: */ while (!group_slide_up(xdf, &g, flags)) if (group_previous(xdfo, &go)) @@ -869,11 +856,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { /* Now shift the group forward as far as possible: */ while (1) { - if (!blank_lines) - blank_lines = is_blank_line( - xdf->recs[g.end - 1], - flags); - if (group_slide_down(xdf, &g, flags)) break; if (group_next(xdfo, &go)) @@ -906,21 +888,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { if (group_previous(xdfo, &go)) xdl_bug("group sync broken sliding to match"); } - } else if ((flags & XDF_COMPACTION_HEURISTIC) && blank_lines) { - /* - * Compaction heuristic: if it is possible to shift the - * group to make its bottom line a blank line, do so. - * - * As we already shifted the group forward as far as - * possible in the earlier loop, we only need to handle - * backward shifts, not forward ones. - */ - while (!is_blank_line(xdf->recs[g.end - 1], flags)) { - if (group_slide_up(xdf, &g, flags)) - xdl_bug("blank line disappeared"); - if (group_previous(xdfo, &go)) - xdl_bug("group sync broken sliding to blank line"); - } } else if (flags & XDF_INDENT_HEURISTIC) { /* * Indent heuristic: a group of pure add/delete lines From 984ad34691f3bedf0badce3bc27e02afe7e8dc1e Mon Sep 17 00:00:00 2001 From: Jordi Mas Date: Sat, 24 Dec 2016 08:01:40 +0100 Subject: [PATCH 0121/1540] l10n: New Catalan translation maintainer Signed-off-by: Jordi Mas --- po/TEAMS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/po/TEAMS b/po/TEAMS index d3e63bc4806d1f..b221402ae3761b 100644 --- a/po/TEAMS +++ b/po/TEAMS @@ -6,8 +6,9 @@ Repository: https://github.com/git-l10n/git-po Leader: Alexander Shopov Language: ca (Catalan) -Repository: https://github.com/alexhenrie/git-po -Leader: Alex Henrie +Repository: https://github.com/Softcatala/git-po +Leader: Jordi Mas +Members: Alex Henrie Language: de (German) Repository: https://github.com/ralfth/git-po-de From b22d7484034670d08f438c9f1376423adce1e29c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 27 Dec 2016 09:12:09 -0800 Subject: [PATCH 0122/1540] lockfile: move REPORT_ON_ERROR bit elsewhere There was LOCK_NO_DEREF defined as 2 = 1<<1 with the same value, which was missed due to a huge comment block. Deconflict by moving the new one to 4 = 1<<2 for now. Signed-off-by: Junio C Hamano --- lockfile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lockfile.h b/lockfile.h index 16775a7d79bba0..7b715f9e775488 100644 --- a/lockfile.h +++ b/lockfile.h @@ -137,7 +137,7 @@ struct lock_file { * ... this flag can be passed instead to return -1 and give the usual * error message upon an error. */ -#define LOCK_REPORT_ON_ERROR 2 +#define LOCK_REPORT_ON_ERROR 4 /* * Usually symbolic links in the destination path are resolved. This From e05806da9ec4aff8adfed142ab2a2b3b02e33c8c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 27 Dec 2016 09:17:51 -0800 Subject: [PATCH 0123/1540] Fourth batch for 2.12 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index 159a1bbb83c423..778145a3ed322f 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -43,6 +43,24 @@ UI, Workflows & Features embedded in these clones of the submodules embedded in the clone of the superproject. + * Porcelain scripts written in Perl are getting internationalized. + + * "git merge --continue" has been added as a synonym to "git commit" + to conclude a merge that has stopped due to conflicts. + + * Finer-grained control of what protocols are allowed for transports + during clone/fetch/push have been enabled via a new configuration + mechanism. + + * "git shortlog" learned "--committer" option to group commits by + committer, instead of author. + + * GitLFS integration with "git p4" has been updated. + + * The isatty() emulation for Windows has been updated to eradicate + the previous hack that depended on internals of (older) MSVC + runtime. + Performance, Internal Implementation, Development Support etc. @@ -202,6 +220,28 @@ notes for details). * Fix for NDEBUG builds. (merge 08414938a2 jt/mailinfo-fold-in-body-headers later to maint). + * A lazy "git push" without refspec did not internally use a fully + specified refspec to perform 'current', 'simple', or 'upstream' + push, causing unnecessary "ambiguous ref" errors. + (merge b284495e93 jc/push-default-explicit later to maint). + + * "git p4" misbehaved when swapping a directory and a symbolic link. + (merge df8a9e86db ld/p4-compare-dir-vs-symlink later to maint). + + * Even though an fix was attempted in Git 2.9.3 days, but running + "git difftool --dir-diff" from a subdirectory never worked. This + has been fixed. + (merge ce6926974e jk/difftool-in-subdir later to maint). + + * "git p4" that tracks multile p4 paths imported a single changelist + that touches files in these multiple paths as one commit, followed + by many empty commits. This has been fixed. + (merge 9943e5b979 gv/p4-multi-path-commit-fix later to maint). + + * A potential but unlikely buffer overflow in Windows port has been + fixed. + (merge c46458e82f mk/mingw-winansi-ttyname-termination-fix later to maint). + * Other minor doc, test and build updates and code cleanups. (merge fa6ca11105 nd/qsort-in-merge-recursive later to maint). (merge fa3142c919 ak/lazy-prereq-mktemp later to maint). @@ -211,3 +251,5 @@ notes for details). (merge 9e189f1a5c sb/t3600-cleanup later to maint). (merge e2c20be57c lr/doc-fix-cet later to maint). (merge 47437fd3bd kh/tutorial-grammofix later to maint). + (merge f2627d9b19 sb/submodule-config-cleanup later to maint). + (merge 7eeda8b821 ls/filter-process later to maint). From 7c4be458b1c7ba81b0cc63d76a144261eb2395be Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 27 Dec 2016 09:50:13 -0800 Subject: [PATCH 0124/1540] worktree: initialize return value for submodule_uses_worktrees When the worktrees directory is empty, the `ret` will be returned uninitialized. Fix it by initializing the value. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worktree.c b/worktree.c index d4606aa8cd5ab4..828fd7a0ad4243 100644 --- a/worktree.c +++ b/worktree.c @@ -387,7 +387,7 @@ int submodule_uses_worktrees(const char *path) struct strbuf sb = STRBUF_INIT; DIR *dir; struct dirent *d; - int ret; + int ret = 0; struct repository_format format; submodule_gitdir = git_pathdup_submodule(path, "%s", ""); From bd26756112c0b9394f0aea0a77e7f713ffb0cd00 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 20 Dec 2016 15:20:09 -0800 Subject: [PATCH 0125/1540] submodule.h: add extern keyword to functions As the upcoming series will add a lot of functions to the submodule header, let's first make the header consistent to the rest of the project by adding the extern keyword to functions. As per the CodingGuidelines we try to stay below 80 characters per line, so adapt all those functions to stay below 80 characters that are already using more than one line. Those function using just one line are better kept in one line than breaking them up into multiple lines just for the goal of staying below the character limit as it makes grepping for functions easier if they are one liners. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- submodule.h | 55 +++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/submodule.h b/submodule.h index 6229054b9928d7..61fb610749f55a 100644 --- a/submodule.h +++ b/submodule.h @@ -29,50 +29,55 @@ struct submodule_update_strategy { }; #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL} -int is_staging_gitmodules_ok(void); -int update_path_in_gitmodules(const char *oldpath, const char *newpath); -int remove_path_from_gitmodules(const char *path); -void stage_updated_gitmodules(void); -void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, +extern int is_staging_gitmodules_ok(void); +extern int update_path_in_gitmodules(const char *oldpath, const char *newpath); +extern int remove_path_from_gitmodules(const char *path); +extern void stage_updated_gitmodules(void); +extern void set_diffopt_flags_from_submodule_config(struct diff_options *, const char *path); -int submodule_config(const char *var, const char *value, void *cb); -void gitmodules_config(void); -int parse_submodule_update_strategy(const char *value, +extern int submodule_config(const char *var, const char *value, void *cb); +extern void gitmodules_config(void); +extern int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); -const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); -void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); -void show_submodule_summary(FILE *f, const char *path, +extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); +extern void handle_ignore_submodules_arg(struct diff_options *, const char *); +extern void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset); -void show_submodule_inline_diff(FILE *f, const char *path, +extern void show_submodule_inline_diff(FILE *f, const char *path, const char *line_prefix, struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset, const struct diff_options *opt); -void set_config_fetch_recurse_submodules(int value); -void check_for_new_submodule_commits(unsigned char new_sha1[20]); -int fetch_populated_submodules(const struct argv_array *options, +extern void set_config_fetch_recurse_submodules(int value); +extern void check_for_new_submodule_commits(unsigned char new_sha1[20]); +extern int fetch_populated_submodules(const struct argv_array *options, const char *prefix, int command_line_option, int quiet, int max_parallel_jobs); -unsigned is_submodule_modified(const char *path, int ignore_untracked); -int submodule_uses_gitfile(const char *path); -int ok_to_remove_submodule(const char *path); -int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], - const unsigned char a[20], const unsigned char b[20], int search); -int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name, - struct string_list *needs_pushing); -int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); -int parallel_submodules(void); +extern unsigned is_submodule_modified(const char *path, int ignore_untracked); +extern int submodule_uses_gitfile(const char *path); +extern int ok_to_remove_submodule(const char *path); +extern int merge_submodule(unsigned char result[20], const char *path, + const unsigned char base[20], + const unsigned char a[20], + const unsigned char b[20], int search); +extern int find_unpushed_submodules(unsigned char new_sha1[20], + const char *remotes_name, + struct string_list *needs_pushing); +extern int push_unpushed_submodules(unsigned char new_sha1[20], + const char *remotes_name); +extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); +extern int parallel_submodules(void); /* * Prepare the "env_array" parameter of a "struct child_process" for executing * a submodule by clearing any repo-specific envirionment variables, but * retaining any config in the environment. */ -void prepare_submodule_repo_env(struct argv_array *out); +extern void prepare_submodule_repo_env(struct argv_array *out); #define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0) extern void absorb_git_dir_into_superproject(const char *prefix, From 5a1c824f70ec261f8f9e5e039555fc80301dee0b Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 20 Dec 2016 15:20:10 -0800 Subject: [PATCH 0126/1540] submodule: modernize ok_to_remove_submodule to use argv_array Instead of constructing the NULL terminated array ourselves, we should make use of the argv_array infrastructure. While at it, adapt the error messages to reflect the actual invocation. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- submodule.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/submodule.c b/submodule.c index 45ccfb7ab4d512..9f0b544ebe9163 100644 --- a/submodule.c +++ b/submodule.c @@ -1023,13 +1023,6 @@ int ok_to_remove_submodule(const char *path) { ssize_t len; struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = { - "status", - "--porcelain", - "-u", - "--ignore-submodules=none", - NULL, - }; struct strbuf buf = STRBUF_INIT; int ok_to_remove = 1; @@ -1039,14 +1032,15 @@ int ok_to_remove_submodule(const char *path) if (!submodule_uses_gitfile(path)) return 0; - cp.argv = argv; + argv_array_pushl(&cp.args, "status", "--porcelain", "-u", + "--ignore-submodules=none", NULL); prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; if (start_command(&cp)) - die("Could not run 'git status --porcelain -uall --ignore-submodules=none' in submodule %s", path); + die(_("could not run 'git status --porcelain -u --ignore-submodules=none' in submodule %s"), path); len = strbuf_read(&buf, cp.out, 1024); if (len > 2) @@ -1054,7 +1048,7 @@ int ok_to_remove_submodule(const char *path) close(cp.out); if (finish_command(&cp)) - die("'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s", path); + die(_("'git status --porcelain -u --ignore-submodules=none' failed in submodule %s"), path); strbuf_release(&buf); return ok_to_remove; From 83b7696605dbea3eb4e878cf904159f398345aa9 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 20 Dec 2016 15:20:11 -0800 Subject: [PATCH 0127/1540] submodule: rename and add flags to ok_to_remove_submodule In different contexts the question "Is it ok to delete a submodule?" may be answered differently. In 293ab15eea (submodule: teach rm to remove submodules unless they contain a git directory, 2012-09-26) a case was made that we can safely ignore ignored untracked files for removal as we explicitely ask for the removal of the submodule. In a later patch we want to remove submodules even when the user doesn't explicitly ask for it (e.g. checking out a tree-ish in which the submodule doesn't exist). In that case we want to be more careful when it comes to deletion of untracked files. As of this patch it is unclear how this will be implemented exactly, so we'll offer flags in which the caller can specify how the different untracked files ought to be handled. As the flags allow the function to not die on an error when spawning a child process, we need to find an appropriate return code for the case when the child process could not be started. As in that case we cannot tell if the submodule is ok to remove, we'd want to return 'false'. As only 0 is understood as false, rename the function to invert the meaning, i.e. the return code of 0 signals the removal of the submodule is fine, and other values can be used to return a more precise answer what went wrong. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/rm.c | 4 +++- submodule.c | 49 +++++++++++++++++++++++++++++++++++++------------ submodule.h | 6 +++++- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/builtin/rm.c b/builtin/rm.c index 3f3e24eb36af03..5a5a66272b767d 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -187,7 +187,9 @@ static int check_local_mod(struct object_id *head, int index_only) */ if (ce_match_stat(ce, &st, 0) || (S_ISGITLINK(ce->ce_mode) && - !ok_to_remove_submodule(ce->name))) + bad_to_remove_submodule(ce->name, + SUBMODULE_REMOVAL_DIE_ON_ERROR | + SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))) local_changes = 1; /* diff --git a/submodule.c b/submodule.c index 9f0b544ebe9163..1cc04d24e5766f 100644 --- a/submodule.c +++ b/submodule.c @@ -1019,39 +1019,64 @@ int submodule_uses_gitfile(const char *path) return 1; } -int ok_to_remove_submodule(const char *path) +/* + * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data + * when doing so. + * + * Return 1 if we'd lose data, return 0 if the removal is fine, + * and negative values for errors. + */ +int bad_to_remove_submodule(const char *path, unsigned flags) { ssize_t len; struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; - int ok_to_remove = 1; + int ret = 0; if (!file_exists(path) || is_empty_dir(path)) - return 1; + return 0; if (!submodule_uses_gitfile(path)) - return 0; + return 1; - argv_array_pushl(&cp.args, "status", "--porcelain", "-u", + argv_array_pushl(&cp.args, "status", "--porcelain", "--ignore-submodules=none", NULL); + + if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED) + argv_array_push(&cp.args, "-uno"); + else + argv_array_push(&cp.args, "-uall"); + + if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)) + argv_array_push(&cp.args, "--ignored"); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; - if (start_command(&cp)) - die(_("could not run 'git status --porcelain -u --ignore-submodules=none' in submodule %s"), path); + if (start_command(&cp)) { + if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) + die(_("could not start 'git status in submodule '%s'"), + path); + ret = -1; + goto out; + } len = strbuf_read(&buf, cp.out, 1024); if (len > 2) - ok_to_remove = 0; + ret = 1; close(cp.out); - if (finish_command(&cp)) - die(_("'git status --porcelain -u --ignore-submodules=none' failed in submodule %s"), path); - + if (finish_command(&cp)) { + if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) + die(_("could not run 'git status in submodule '%s'"), + path); + ret = -1; + } +out: strbuf_release(&buf); - return ok_to_remove; + return ret; } static int find_first_merges(struct object_array *result, const char *path, diff --git a/submodule.h b/submodule.h index 61fb610749f55a..21b1569413829b 100644 --- a/submodule.h +++ b/submodule.h @@ -59,7 +59,11 @@ extern int fetch_populated_submodules(const struct argv_array *options, int quiet, int max_parallel_jobs); extern unsigned is_submodule_modified(const char *path, int ignore_untracked); extern int submodule_uses_gitfile(const char *path); -extern int ok_to_remove_submodule(const char *path); + +#define SUBMODULE_REMOVAL_DIE_ON_ERROR (1<<0) +#define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1) +#define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2) +extern int bad_to_remove_submodule(const char *path, unsigned flags); extern int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], const unsigned char a[20], From 55856a35b20dae2499d4e0b23551c7ba9a33baf2 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 27 Dec 2016 11:03:14 -0800 Subject: [PATCH 0128/1540] rm: absorb a submodules git dir before deletion When deleting a submodule, we need to keep the actual git directory around, such that we do not lose local changes in there and at a later checkout of the submodule we don't need to clone it again. Now that the functionality is available to absorb the git directory of a submodule, rewrite the checking in git-rm to not complain, but rather relocate the git directories inside the superproject. An alternative solution was discussed to have a function `depopulate_submodule`. That would couple the check for its git directory and possible relocation before the the removal, such that it is less likely to miss the check in the future. But the indirection with such a function added seemed also complex. The reason for that was that this possible move of the git directory was also implemented in `ok_to_remove_submodule`, such that this function could truthfully answer whether it is ok to remove the submodule. The solution proposed here defers all these checks to the caller. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/rm.c | 80 ++++++++++++--------------------------------------- t/t3600-rm.sh | 43 +++++++++++---------------- 2 files changed, 36 insertions(+), 87 deletions(-) diff --git a/builtin/rm.c b/builtin/rm.c index 5a5a66272b767d..20635dca94e874 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -59,27 +59,9 @@ static void print_error_files(struct string_list *files_list, } } -static void error_removing_concrete_submodules(struct string_list *files, int *errs) -{ - print_error_files(files, - Q_("the following submodule (or one of its nested " - "submodules)\n" - "uses a .git directory:", - "the following submodules (or one of their nested " - "submodules)\n" - "use a .git directory:", files->nr), - _("\n(use 'rm -rf' if you really want to remove " - "it including all of its history)"), - errs); - string_list_clear(files, 0); -} - -static int check_submodules_use_gitfiles(void) +static void submodules_absorb_gitdir_if_needed(const char *prefix) { int i; - int errs = 0; - struct string_list files = STRING_LIST_INIT_NODUP; - for (i = 0; i < list.nr; i++) { const char *name = list.entry[i].name; int pos; @@ -99,12 +81,9 @@ static int check_submodules_use_gitfiles(void) continue; if (!submodule_uses_gitfile(name)) - string_list_append(&files, name); + absorb_git_dir_into_superproject(prefix, name, + ABSORB_GITDIR_RECURSE_SUBMODULES); } - - error_removing_concrete_submodules(&files, &errs); - - return errs; } static int check_local_mod(struct object_id *head, int index_only) @@ -120,7 +99,6 @@ static int check_local_mod(struct object_id *head, int index_only) int errs = 0; struct string_list files_staged = STRING_LIST_INIT_NODUP; struct string_list files_cached = STRING_LIST_INIT_NODUP; - struct string_list files_submodule = STRING_LIST_INIT_NODUP; struct string_list files_local = STRING_LIST_INIT_NODUP; no_head = is_null_oid(head); @@ -219,13 +197,8 @@ static int check_local_mod(struct object_id *head, int index_only) else if (!index_only) { if (staged_changes) string_list_append(&files_cached, name); - if (local_changes) { - if (S_ISGITLINK(ce->ce_mode) && - !submodule_uses_gitfile(name)) - string_list_append(&files_submodule, name); - else - string_list_append(&files_local, name); - } + if (local_changes) + string_list_append(&files_local, name); } } print_error_files(&files_staged, @@ -247,8 +220,6 @@ static int check_local_mod(struct object_id *head, int index_only) &errs); string_list_clear(&files_cached, 0); - error_removing_concrete_submodules(&files_submodule, &errs); - print_error_files(&files_local, Q_("the following file has local modifications:", "the following files have local modifications:", @@ -342,6 +313,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) exit(0); } + if (!index_only) + submodules_absorb_gitdir_if_needed(prefix); + /* * If not forced, the file, the index and the HEAD (if exists) * must match; but the file can already been removed, since @@ -358,9 +332,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) oidclr(&oid); if (check_local_mod(&oid, index_only)) exit(1); - } else if (!index_only) { - if (check_submodules_use_gitfiles()) - exit(1); } /* @@ -389,32 +360,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix) */ if (!index_only) { int removed = 0, gitmodules_modified = 0; - struct strbuf buf = STRBUF_INIT; for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { - if (is_empty_dir(path)) { - if (!rmdir(path)) { - removed = 1; - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - continue; - } - } else { - strbuf_reset(&buf); - strbuf_addstr(&buf, path); - if (!remove_dir_recursively(&buf, 0)) { - removed = 1; - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - strbuf_release(&buf); - continue; - } else if (!file_exists(path)) - /* Submodule was removed by user */ - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - /* Fallthrough and let remove_path() fail. */ - } + struct strbuf buf = STRBUF_INIT; + + strbuf_addstr(&buf, path); + if (remove_dir_recursively(&buf, 0)) + die(_("could not remove '%s'"), path); + strbuf_release(&buf); + + removed = 1; + if (!remove_path_from_gitmodules(path)) + gitmodules_modified = 1; + continue; } if (!remove_path(path)) { removed = 1; @@ -423,7 +382,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!removed) die_errno("git rm: '%s'", path); } - strbuf_release(&buf); if (gitmodules_modified) stage_updated_gitmodules(); } diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 14f0edca2b6f67..030d6c32ae94b2 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -585,26 +585,22 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' ' test_cmp expect actual ' -test_expect_success 'rm of a populated submodule with a .git directory fails even when forced' ' +test_expect_success 'rm of a populated submodule with a .git directory migrates git dir' ' git checkout -f master && git reset --hard && git submodule update && (cd submod && rm .git && cp -R ../.git/modules/sub .git && - GIT_WORK_TREE=. git config --unset core.worktree + GIT_WORK_TREE=. git config --unset core.worktree && + rm -r ../.git/modules/sub ) && - test_must_fail git rm submod && - test -d submod && - test -d submod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - test_must_fail git rm -f submod && - test -d submod && - test -d submod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - rm -rf submod + git rm submod 2>output.err && + ! test -d submod && + ! test -d submod/.git && + git status -s -uno --ignore-submodules=none >actual && + test -s actual && + test_i18ngrep Migrating output.err ' cat >expect.deepmodified < actual && - ! test -s actual && - test_must_fail git rm -f submod && - test -d submod && - test -d submod/subsubmod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - rm -rf submod + git rm submod 2>output.err && + ! test -d submod && + ! test -d submod/subsubmod/.git && + git status -s -uno --ignore-submodules=none >actual && + test -s actual && + test_i18ngrep Migrating output.err ' test_expect_success 'checking out a commit after submodule removal needs manual updates' ' - git commit -m "submodule removal" submod && + git commit -m "submodule removal" submod .gitmodules && git checkout HEAD^ && git submodule update && git checkout -q HEAD^ 2>actual && From bdf56de896b48d6fe3ad90e92138b5fd6579748d Mon Sep 17 00:00:00 2001 From: David Turner Date: Wed, 28 Dec 2016 17:45:41 -0500 Subject: [PATCH 0129/1540] auto gc: don't write bitmaps for incremental repacks When git gc --auto does an incremental repack of loose objects, we do not expect to be able to write a bitmap; it is very likely that objects in the new pack will have references to objects outside of the pack. So we shouldn't try to write a bitmap, because doing so will likely issue a warning. This warning was making its way into gc.log. When the gc.log was present, future auto gc runs would refuse to run. Patch by Jeff King. Bug report, test, and commit message by David Turner. Signed-off-by: David Turner Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/gc.c | 9 ++++++++- t/t6500-gc.sh | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/builtin/gc.c b/builtin/gc.c index 069950d0b417f5..331f2192607a4f 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -191,6 +191,11 @@ static void add_repack_all_option(void) } } +static void add_repack_incremental_option(void) +{ + argv_array_push(&repack, "--no-write-bitmap-index"); +} + static int need_to_gc(void) { /* @@ -208,7 +213,9 @@ static int need_to_gc(void) */ if (too_many_packs()) add_repack_all_option(); - else if (!too_many_loose_objects()) + else if (too_many_loose_objects()) + add_repack_incremental_option(); + else return 0; if (run_hook_le(NULL, "pre-auto-gc", NULL)) diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 5d7d4146179bb4..1762dfa6a306ca 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -43,4 +43,29 @@ test_expect_success 'gc is not aborted due to a stale symref' ' ) ' +test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' ' + test_config gc.auto 3 && + test_config gc.autodetach false && + test_config pack.writebitmaps true && + # We need to create two object whose sha1s start with 17 + # since this is what git gc counts. As it happens, these + # two blobs will do so. + test_commit 263 && + test_commit 410 && + # Our first gc will create a pack; our second will create a second pack + git gc --auto && + ls .git/objects/pack | sort >existing_packs && + test_commit 523 && + test_commit 790 && + + git gc --auto 2>err && + test_i18ngrep ! "^warning:" err && + ls .git/objects/pack/ | sort >post_packs && + comm -1 -3 existing_packs post_packs >new && + comm -2 -3 existing_packs post_packs >del && + test_line_count = 0 del && # No packs are deleted + test_line_count = 2 new # There is one new pack and its .idx +' + + test_done From 1c409a705cb30ae3db6cdd48757c4a85f51456d4 Mon Sep 17 00:00:00 2001 From: David Turner Date: Wed, 28 Dec 2016 17:45:42 -0500 Subject: [PATCH 0130/1540] repack: die on incremental + write-bitmap-index The bitmap index only works for single packs, so requesting an incremental repack with bitmap indexes makes no sense. Signed-off-by: David Turner Signed-off-by: Junio C Hamano --- builtin/repack.c | 9 +++++++++ t/t5310-pack-bitmaps.sh | 8 +++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/builtin/repack.c b/builtin/repack.c index 80dd06b4a2a8b7..677bc7c81a2be1 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -18,6 +18,12 @@ static const char *const git_repack_usage[] = { NULL }; +static const char incremental_bitmap_conflict_error[] = N_( +"Incremental repacks are incompatible with bitmap indexes. Use\n" +"--no-write-bitmap-index or disable the pack.writebitmaps configuration." +); + + static int repack_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "repack.usedeltabaseoffset")) { @@ -206,6 +212,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps; + if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) + die(_(incremental_bitmap_conflict_error)); + packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 3893afd687986e..03b6552c424d13 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -105,12 +105,10 @@ test_expect_success 'fetch (partial bitmap)' ' test_cmp expect actual ' -test_expect_success 'incremental repack cannot create bitmaps' ' +test_expect_success 'incremental repack fails when bitmaps are requested' ' test_commit more-1 && - find .git/objects/pack -name "*.bitmap" >expect && - git repack -d && - find .git/objects/pack -name "*.bitmap" >actual && - test_cmp expect actual + test_must_fail git repack -d 2>err && + test_i18ngrep "Incremental repacks are incompatible with bitmap" err ' test_expect_success 'incremental repack can disable bitmaps' ' From bc233524c96518c04650d297ad81a9c948fc2e41 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Thu, 29 Dec 2016 12:22:23 +0200 Subject: [PATCH 0131/1540] git-p4: do not pass '-r 0' to p4 commands git-p4 crashes when used with a very old p4 client version that does not support the '-r ' option in its commands. Allow making git-p4 work with old p4 clients by setting git-p4.retries to 0. Alternatively git-p4.retries could be made opt-in. But since only very old, barely maintained p4 versions don't support the '-r' option, the setting-retries-to-0 workaround would do. The "-r retries" option is present in Perforce 2012.2 Command Reference, but absent from Perforce 2012.1 Command Reference. Signed-off-by: Igor Kushnir Acked-by: Lars Schneider Reviewed-by: Luke Diamand Signed-off-by: Junio C Hamano --- Documentation/git-p4.txt | 2 ++ git-p4.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 656587248cc466..5352cae4b48948 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -470,6 +470,8 @@ git-p4.client:: git-p4.retries:: Specifies the number of times to retry a p4 command (notably, 'p4 sync') if the network times out. The default value is 3. + Set the value to 0 to disable retries or if your p4 version + does not support retries (pre 2012.2). Clone and sync variables ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/git-p4.py b/git-p4.py index 2422178210f344..2d39d10122caa2 100755 --- a/git-p4.py +++ b/git-p4.py @@ -82,7 +82,9 @@ def p4_build_cmd(cmd): if retries is None: # Perform 3 retries by default retries = 3 - real_cmd += ["-r", str(retries)] + if retries > 0: + # Provide a way to not pass this option by setting git-p4.retries to 0 + real_cmd += ["-r", str(retries)] if isinstance(cmd,basestring): real_cmd = ' '.join(real_cmd) + ' ' + cmd From 8fef3f36b779866578d5661d5f4aac7be59f66cd Mon Sep 17 00:00:00 2001 From: Dimitriy Ryazantcev Date: Thu, 15 Dec 2016 00:34:26 +0200 Subject: [PATCH 0132/1540] gitk: ru.po: Update Russian translation Signed-off-by: Dimitriy Ryazantcev Signed-off-by: Paul Mackerras --- po/ru.po | 670 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 356 insertions(+), 314 deletions(-) diff --git a/po/ru.po b/po/ru.po index 8e669e045a5022..9b08c263eadea7 100644 --- a/po/ru.po +++ b/po/ru.po @@ -3,15 +3,15 @@ # Translators: # 0xAX , 2014 # Alex Riesen , 2015 -# Dimitriy Ryazantcev , 2015 +# Dimitriy Ryazantcev , 2015-2016 # Dmitry Potapov , 2009 # Skip , 2011 msgid "" msgstr "" "Project-Id-Version: Git Russian Localization Project\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-17 14:32+1000\n" -"PO-Revision-Date: 2015-10-12 10:14+0000\n" +"POT-Creation-Date: 2016-12-15 00:18+0200\n" +"PO-Revision-Date: 2016-12-14 22:23+0000\n" "Last-Translator: Dimitriy Ryazantcev \n" "Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n" "MIME-Version: 1.0\n" @@ -24,11 +24,11 @@ msgstr "" msgid "Couldn't get list of unmerged files:" msgstr "Невозможно получить список файлов незавершённой операции слияния:" -#: gitk:212 gitk:2381 +#: gitk:212 gitk:2403 msgid "Color words" msgstr "Цветные слова" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 +#: gitk:217 gitk:2403 gitk:8249 gitk:8282 msgid "Markup words" msgstr "Помеченые слова" @@ -58,1272 +58,1314 @@ msgstr "Ошибка запуска git log:" msgid "Reading" msgstr "Чтение" -#: gitk:496 gitk:4525 +#: gitk:496 gitk:4549 msgid "Reading commits..." msgstr "Чтение коммитов..." -#: gitk:499 gitk:1637 gitk:4528 +#: gitk:499 gitk:1641 gitk:4552 msgid "No commits selected" msgstr "Ничего не выбрано" -#: gitk:1445 gitk:4045 gitk:12432 +#: gitk:1449 gitk:4069 gitk:12583 msgid "Command line" msgstr "Командная строка" -#: gitk:1511 +#: gitk:1515 msgid "Can't parse git log output:" msgstr "Ошибка обработки вывода команды git log:" -#: gitk:1740 +#: gitk:1744 msgid "No commit information available" msgstr "Нет информации о коммите" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 +#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668 msgid "OK" msgstr "Ok" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 +#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791 +#: gitk:11389 gitk:11669 msgid "Cancel" msgstr "Отмена" -#: gitk:2069 +#: gitk:2087 msgid "&Update" msgstr "Обновить" -#: gitk:2070 +#: gitk:2088 msgid "&Reload" msgstr "Перечитать" -#: gitk:2071 +#: gitk:2089 msgid "Reread re&ferences" msgstr "Обновить список ссылок" -#: gitk:2072 +#: gitk:2090 msgid "&List references" msgstr "Список ссылок" -#: gitk:2074 +#: gitk:2092 msgid "Start git &gui" msgstr "Запустить git gui" -#: gitk:2076 +#: gitk:2094 msgid "&Quit" msgstr "Завершить" -#: gitk:2068 +#: gitk:2086 msgid "&File" msgstr "Файл" -#: gitk:2080 +#: gitk:2098 msgid "&Preferences" msgstr "Настройки" -#: gitk:2079 +#: gitk:2097 msgid "&Edit" msgstr "Редактировать" -#: gitk:2084 +#: gitk:2102 msgid "&New view..." msgstr "Новое представление..." -#: gitk:2085 +#: gitk:2103 msgid "&Edit view..." msgstr "Редактировать представление..." -#: gitk:2086 +#: gitk:2104 msgid "&Delete view" msgstr "Удалить представление" -#: gitk:2088 gitk:4043 +#: gitk:2106 msgid "&All files" msgstr "Все файлы" -#: gitk:2083 gitk:4067 +#: gitk:2101 msgid "&View" msgstr "Представление" -#: gitk:2093 gitk:2103 gitk:3012 +#: gitk:2111 gitk:2121 msgid "&About gitk" msgstr "О gitk" -#: gitk:2094 gitk:2108 +#: gitk:2112 gitk:2126 msgid "&Key bindings" msgstr "Назначения клавиатуры" -#: gitk:2092 gitk:2107 +#: gitk:2110 gitk:2125 msgid "&Help" msgstr "Подсказка" -#: gitk:2185 gitk:8652 +#: gitk:2203 gitk:8681 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 +#: gitk:2247 msgid "Row" msgstr "Строка" -#: gitk:2267 +#: gitk:2285 msgid "Find" msgstr "Поиск" -#: gitk:2295 +#: gitk:2313 msgid "commit" msgstr "коммит" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 +#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851 +#: gitk:6936 msgid "containing:" msgstr "содержащее:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 +#: gitk:2320 gitk:3550 gitk:3555 gitk:4787 msgid "touching paths:" msgstr "касательно файлов:" -#: gitk:2303 gitk:4777 +#: gitk:2321 gitk:4801 msgid "adding/removing string:" msgstr "добавив/удалив строку:" -#: gitk:2304 gitk:4779 +#: gitk:2322 gitk:4803 msgid "changing lines matching:" msgstr "изменяя совпадающие строки:" -#: gitk:2313 gitk:2315 gitk:4766 +#: gitk:2331 gitk:2333 gitk:4790 msgid "Exact" msgstr "Точно" -#: gitk:2315 gitk:4854 gitk:6723 +#: gitk:2333 gitk:4878 gitk:6747 msgid "IgnCase" msgstr "Игнорировать большие/маленькие" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 +#: gitk:2333 gitk:4760 gitk:4876 gitk:6743 msgid "Regexp" msgstr "Регулярные выражения" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 +#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940 msgid "All fields" msgstr "Во всех полях" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 +#: gitk:2336 gitk:4895 gitk:4928 gitk:6810 msgid "Headline" msgstr "Заголовок" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 +#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413 msgid "Comments" msgstr "Комментарии" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 +#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859 +#: gitk:8874 msgid "Author" msgstr "Автор" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 +#: gitk:2337 gitk:4895 gitk:6810 gitk:7350 msgid "Committer" msgstr "Коммитер" -#: gitk:2350 +#: gitk:2371 msgid "Search" msgstr "Найти" -#: gitk:2358 +#: gitk:2379 msgid "Diff" msgstr "Сравнить" -#: gitk:2360 +#: gitk:2381 msgid "Old version" msgstr "Старая версия" -#: gitk:2362 +#: gitk:2383 msgid "New version" msgstr "Новая версия" -#: gitk:2364 +#: gitk:2386 msgid "Lines of context" msgstr "Строк контекста" -#: gitk:2374 +#: gitk:2396 msgid "Ignore space change" msgstr "Игнорировать пробелы" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 +#: gitk:2400 gitk:2402 gitk:7983 gitk:8235 msgid "Line diff" msgstr "Изменения строк" -#: gitk:2445 +#: gitk:2467 msgid "Patch" msgstr "Патч" -#: gitk:2447 +#: gitk:2469 msgid "Tree" msgstr "Файлы" -#: gitk:2617 gitk:2637 +#: gitk:2639 gitk:2660 msgid "Diff this -> selected" msgstr "Сравнить этот коммит с выделенным" -#: gitk:2618 gitk:2638 +#: gitk:2640 gitk:2661 msgid "Diff selected -> this" msgstr "Сравнить выделенный с этим коммитом" -#: gitk:2619 gitk:2639 +#: gitk:2641 gitk:2662 msgid "Make patch" msgstr "Создать патч" -#: gitk:2620 gitk:9254 +#: gitk:2642 gitk:9283 msgid "Create tag" msgstr "Создать метку" -#: gitk:2621 gitk:9371 +#: gitk:2643 +msgid "Copy commit summary" +msgstr "Копировать информацию о коммите" + +#: gitk:2644 gitk:9414 msgid "Write commit to file" msgstr "Сохранить коммит в файл" -#: gitk:2622 gitk:9428 +#: gitk:2645 msgid "Create new branch" msgstr "Создать ветку" -#: gitk:2623 +#: gitk:2646 msgid "Cherry-pick this commit" -msgstr "Отбор лучшего для этого коммита" +msgstr "Копировать этот коммит в текущую ветку" -#: gitk:2624 +#: gitk:2647 msgid "Reset HEAD branch to here" msgstr "Установить HEAD на этот коммит" -#: gitk:2625 +#: gitk:2648 msgid "Mark this commit" msgstr "Пометить этот коммит" -#: gitk:2626 +#: gitk:2649 msgid "Return to mark" msgstr "Вернуться на пометку" -#: gitk:2627 +#: gitk:2650 msgid "Find descendant of this and mark" msgstr "Найти и пометить потомка этого коммита" -#: gitk:2628 +#: gitk:2651 msgid "Compare with marked commit" msgstr "Сравнить с помеченным коммитом" -#: gitk:2629 gitk:2640 +#: gitk:2652 gitk:2663 msgid "Diff this -> marked commit" msgstr "Сравнить выделенное с помеченным коммитом" -#: gitk:2630 gitk:2641 +#: gitk:2653 gitk:2664 msgid "Diff marked commit -> this" msgstr "Сравнить помеченный с этим коммитом" -#: gitk:2631 +#: gitk:2654 msgid "Revert this commit" -msgstr "Возврат этого коммита" +msgstr "Обратить изменения этого коммита" -#: gitk:2647 +#: gitk:2670 msgid "Check out this branch" msgstr "Перейти на эту ветку" -#: gitk:2648 +#: gitk:2671 +msgid "Rename this branch" +msgstr "Переименовать эту ветку" + +#: gitk:2672 msgid "Remove this branch" msgstr "Удалить эту ветку" -#: gitk:2649 +#: gitk:2673 msgid "Copy branch name" msgstr "Копировать имя ветки" -#: gitk:2656 +#: gitk:2680 msgid "Highlight this too" msgstr "Подсветить этот тоже" -#: gitk:2657 +#: gitk:2681 msgid "Highlight this only" msgstr "Подсветить только этот" -#: gitk:2658 +#: gitk:2682 msgid "External diff" msgstr "Программа сравнения" -#: gitk:2659 +#: gitk:2683 msgid "Blame parent commit" msgstr "Авторы изменений родительского коммита" -#: gitk:2660 +#: gitk:2684 msgid "Copy path" msgstr "Копировать путь" -#: gitk:2667 +#: gitk:2691 msgid "Show origin of this line" msgstr "Показать источник этой строки" -#: gitk:2668 +#: gitk:2692 msgid "Run git gui blame on this line" msgstr "Запустить git gui blame для этой строки" -#: gitk:3014 +#: gitk:3036 +msgid "About gitk" +msgstr "О gitk" + +#: gitk:3038 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright 2005-2016 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" -msgstr "\nGitk - программа просмотра истории репозиториев git\n\n© 2005-2016 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" +msgstr "\nGitk — программа просмотра истории репозиториев git\n\n© 2005-2016 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" -#: gitk:3022 gitk:3089 gitk:9857 +#: gitk:3046 gitk:3113 gitk:10004 msgid "Close" msgstr "Закрыть" -#: gitk:3043 +#: gitk:3067 msgid "Gitk key bindings" msgstr "Назначения клавиатуры в Gitk" -#: gitk:3046 +#: gitk:3070 msgid "Gitk key bindings:" msgstr "Назначения клавиатуры в Gitk:" -#: gitk:3048 +#: gitk:3072 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tЗавершить" -#: gitk:3049 +#: gitk:3073 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tЗакрыть окно" -#: gitk:3050 +#: gitk:3074 msgid "\t\tMove to first commit" msgstr "\t\tПерейти к первому коммиту" -#: gitk:3051 +#: gitk:3075 msgid "\t\tMove to last commit" msgstr "\t\tПерейти к последнему коммиту" -#: gitk:3052 +#: gitk:3076 msgid ", p, k\tMove up one commit" msgstr ", p, k\tПерейти на один коммит вверх" -#: gitk:3053 +#: gitk:3077 msgid ", n, j\tMove down one commit" msgstr ", n, j\tПерейти на один коммит вниз" -#: gitk:3054 +#: gitk:3078 msgid ", z, h\tGo back in history list" msgstr ", z, h\tПоказать ранее посещённое состояние" -#: gitk:3055 +#: gitk:3079 msgid ", x, l\tGo forward in history list" msgstr ", x, l\tПоказать следующий посещённый коммит" -#: gitk:3056 +#: gitk:3080 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tПерейти на n родителя от текущего коммита" -#: gitk:3057 +#: gitk:3081 msgid "\tMove up one page in commit list" msgstr "\tПерейти на страницу выше в списке коммитов" -#: gitk:3058 +#: gitk:3082 msgid "\tMove down one page in commit list" msgstr "\tПерейти на страницу ниже в списке коммитов" -#: gitk:3059 +#: gitk:3083 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tПерейти на начало списка коммитов" -#: gitk:3060 +#: gitk:3084 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tПерейти на конец списка коммитов" -#: gitk:3061 +#: gitk:3085 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tПровернуть список коммитов вверх" -#: gitk:3062 +#: gitk:3086 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tПровернуть список коммитов вниз" -#: gitk:3063 +#: gitk:3087 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tПровернуть список коммитов на страницу вверх" -#: gitk:3064 +#: gitk:3088 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tПровернуть список коммитов на страницу вниз" -#: gitk:3065 +#: gitk:3089 msgid "\tFind backwards (upwards, later commits)" msgstr "\tПоиск в обратном порядке (вверх, среди новых коммитов)" -#: gitk:3066 +#: gitk:3090 msgid "\tFind forwards (downwards, earlier commits)" msgstr "\tПоиск (вниз, среди старых коммитов)" -#: gitk:3067 +#: gitk:3091 msgid ", b\tScroll diff view up one page" msgstr ", b\tПрокрутить список изменений на страницу выше" -#: gitk:3068 +#: gitk:3092 msgid "\tScroll diff view up one page" msgstr "\tПрокрутить список изменений на страницу выше" -#: gitk:3069 +#: gitk:3093 msgid "\t\tScroll diff view down one page" msgstr "\t\tПрокрутить список изменений на страницу ниже" -#: gitk:3070 +#: gitk:3094 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tПрокрутить список изменений на 18 строк вверх" -#: gitk:3071 +#: gitk:3095 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tПрокрутить список изменений на 18 строк вниз" -#: gitk:3072 +#: gitk:3096 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tПоиск" -#: gitk:3073 +#: gitk:3097 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tПерейти к следующему найденному коммиту" -#: gitk:3074 +#: gitk:3098 msgid "\tMove to next find hit" msgstr "\tПерейти к следующему найденному коммиту" -#: gitk:3075 +#: gitk:3099 msgid "g\t\tGo to commit" msgstr "g\t\tПерейти на коммит" -#: gitk:3076 +#: gitk:3100 msgid "/\t\tFocus the search box" msgstr "/\t\tПерейти к полю поиска" -#: gitk:3077 +#: gitk:3101 msgid "?\t\tMove to previous find hit" msgstr "?\t\tПерейти к предыдущему найденному коммиту" -#: gitk:3078 +#: gitk:3102 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tПрокрутить список изменений к следующему файлу" -#: gitk:3079 +#: gitk:3103 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tПродолжить поиск в списке изменений" -#: gitk:3080 +#: gitk:3104 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tПерейти к предыдущему найденному тексту в списке изменений" -#: gitk:3081 +#: gitk:3105 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tУвеличить размер шрифта" -#: gitk:3082 +#: gitk:3106 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tУвеличить размер шрифта" -#: gitk:3083 +#: gitk:3107 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tУменьшить размер шрифта" -#: gitk:3084 +#: gitk:3108 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tУменьшить размер шрифта" -#: gitk:3085 +#: gitk:3109 msgid "\t\tUpdate" msgstr "\t\tОбновить" -#: gitk:3550 gitk:3559 +#: gitk:3574 gitk:3583 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Ошибка создания временного каталога %s:" -#: gitk:3572 +#: gitk:3596 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Ошибка получения «%s» из %s:" -#: gitk:3635 +#: gitk:3659 msgid "command failed:" msgstr "ошибка выполнения команды:" -#: gitk:3784 +#: gitk:3808 msgid "No such commit" msgstr "Коммит не найден" -#: gitk:3798 +#: gitk:3822 msgid "git gui blame: command failed:" msgstr "git gui blame: ошибка выполнения команды:" -#: gitk:3829 +#: gitk:3853 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Ошибка чтения MERGE_HEAD: %s" -#: gitk:3837 +#: gitk:3861 #, tcl-format msgid "Error reading index: %s" msgstr "Ошибка чтения индекса: %s" -#: gitk:3862 +#: gitk:3886 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Ошибка запуска git blame: %s" -#: gitk:3865 gitk:6754 +#: gitk:3889 gitk:6778 msgid "Searching" msgstr "Поиск" -#: gitk:3897 +#: gitk:3921 #, tcl-format msgid "Error running git blame: %s" msgstr "Ошибка выполнения git blame: %s" -#: gitk:3925 +#: gitk:3949 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Эта строка принадлежит коммиту %s, который не показан в этом представлении" -#: gitk:3939 +#: gitk:3963 msgid "External diff viewer failed:" msgstr "Ошибка выполнения программы сравнения:" -#: gitk:4070 +#: gitk:4067 +msgid "All files" +msgstr "Все файлы" + +#: gitk:4091 +msgid "View" +msgstr "Представление" + +#: gitk:4094 msgid "Gitk view definition" msgstr "Gitk определение представлений" -#: gitk:4074 +#: gitk:4098 msgid "Remember this view" msgstr "Запомнить представление" -#: gitk:4075 +#: gitk:4099 msgid "References (space separated list):" msgstr "Ссылки (разделённые пробелом):" -#: gitk:4076 +#: gitk:4100 msgid "Branches & tags:" msgstr "Ветки и метки" -#: gitk:4077 +#: gitk:4101 msgid "All refs" msgstr "Все ссылки" -#: gitk:4078 +#: gitk:4102 msgid "All (local) branches" msgstr "Все (локальные) ветки" -#: gitk:4079 +#: gitk:4103 msgid "All tags" msgstr "Все метки" -#: gitk:4080 +#: gitk:4104 msgid "All remote-tracking branches" msgstr "Все внешние отслеживаемые ветки" -#: gitk:4081 +#: gitk:4105 msgid "Commit Info (regular expressions):" msgstr "Информация о коммите (регулярные выражения):" -#: gitk:4082 +#: gitk:4106 msgid "Author:" msgstr "Автор:" -#: gitk:4083 +#: gitk:4107 msgid "Committer:" msgstr "Коммитер:" -#: gitk:4084 +#: gitk:4108 msgid "Commit Message:" msgstr "Сообщение коммита:" -#: gitk:4085 +#: gitk:4109 msgid "Matches all Commit Info criteria" msgstr "Совпадает со всеми условиями информации о коммите" -#: gitk:4086 +#: gitk:4110 msgid "Matches no Commit Info criteria" msgstr "Не совпадает с условиями информации о коммите" -#: gitk:4087 +#: gitk:4111 msgid "Changes to Files:" msgstr "Изменения файлов:" -#: gitk:4088 +#: gitk:4112 msgid "Fixed String" msgstr "Обычная строка" -#: gitk:4089 +#: gitk:4113 msgid "Regular Expression" msgstr "Регулярное выражение:" -#: gitk:4090 +#: gitk:4114 msgid "Search string:" msgstr "Строка для поиска:" -#: gitk:4091 +#: gitk:4115 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" msgstr "Даты коммита («2 недели назад», «2009-03-17 15:27:38», «17 марта 2009 15:27:38»):" -#: gitk:4092 +#: gitk:4116 msgid "Since:" msgstr "С даты:" -#: gitk:4093 +#: gitk:4117 msgid "Until:" msgstr "По дату:" -#: gitk:4094 +#: gitk:4118 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Ограничить и/или пропустить количество редакций (положительное число):" -#: gitk:4095 +#: gitk:4119 msgid "Number to show:" msgstr "Показать количество:" -#: gitk:4096 +#: gitk:4120 msgid "Number to skip:" msgstr "Пропустить количество:" -#: gitk:4097 +#: gitk:4121 msgid "Miscellaneous options:" msgstr "Различные опции:" -#: gitk:4098 +#: gitk:4122 msgid "Strictly sort by date" msgstr "Строгая сортировка по дате" -#: gitk:4099 +#: gitk:4123 msgid "Mark branch sides" msgstr "Отметить стороны веток" -#: gitk:4100 +#: gitk:4124 msgid "Limit to first parent" msgstr "Ограничить первым предком" -#: gitk:4101 +#: gitk:4125 msgid "Simple history" msgstr "Упрощенная история" -#: gitk:4102 +#: gitk:4126 msgid "Additional arguments to git log:" msgstr "Дополнительные аргументы для git log:" -#: gitk:4103 +#: gitk:4127 msgid "Enter files and directories to include, one per line:" msgstr "Файлы и каталоги для ограничения истории, по одному на строку:" -#: gitk:4104 +#: gitk:4128 msgid "Command to generate more commits to include:" msgstr "Дополнительная команда для списка коммитов:" -#: gitk:4228 +#: gitk:4252 msgid "Gitk: edit view" msgstr "Gitk: изменить представление" -#: gitk:4236 +#: gitk:4260 msgid "-- criteria for selecting revisions" msgstr "— критерий поиска редакций" -#: gitk:4241 +#: gitk:4265 msgid "View Name" msgstr "Имя представления" -#: gitk:4316 +#: gitk:4340 msgid "Apply (F5)" msgstr "Применить (F5)" -#: gitk:4354 +#: gitk:4378 msgid "Error in commit selection arguments:" msgstr "Ошибка в параметрах выбора коммитов:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 +#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525 msgid "None" msgstr "Ни одного" -#: gitk:5021 gitk:5026 +#: gitk:5045 gitk:5050 msgid "Descendant" msgstr "Порождённое" -#: gitk:5022 +#: gitk:5046 msgid "Not descendant" msgstr "Не порождённое" -#: gitk:5029 gitk:5034 +#: gitk:5053 gitk:5058 msgid "Ancestor" msgstr "Предок" -#: gitk:5030 +#: gitk:5054 msgid "Not ancestor" msgstr "Не предок" -#: gitk:5324 +#: gitk:5348 msgid "Local changes checked in to index but not committed" msgstr "Проиндексированные изменения" -#: gitk:5360 +#: gitk:5384 msgid "Local uncommitted changes, not checked in to index" msgstr "Непроиндексированные изменения" -#: gitk:7134 +#: gitk:7158 msgid "and many more" msgstr "и многое другое" -#: gitk:7137 +#: gitk:7161 msgid "many" msgstr "много" -#: gitk:7328 +#: gitk:7352 msgid "Tags:" msgstr "Метки:" -#: gitk:7345 gitk:7351 gitk:8825 +#: gitk:7369 gitk:7375 gitk:8854 msgid "Parent" msgstr "Предок" -#: gitk:7356 +#: gitk:7380 msgid "Child" msgstr "Потомок" -#: gitk:7365 +#: gitk:7389 msgid "Branch" msgstr "Ветка" -#: gitk:7368 +#: gitk:7392 msgid "Follows" msgstr "Следует за" -#: gitk:7371 +#: gitk:7395 msgid "Precedes" msgstr "Предшествует" -#: gitk:7966 +#: gitk:7990 #, tcl-format msgid "Error getting diffs: %s" msgstr "Ошибка получения изменений: %s" -#: gitk:8650 +#: gitk:8679 msgid "Goto:" msgstr "Перейти к:" -#: gitk:8671 +#: gitk:8700 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Сокращённый SHA1 идентификатор %s неоднозначен" -#: gitk:8678 +#: gitk:8707 #, tcl-format msgid "Revision %s is not known" msgstr "Редакция %s не найдена" -#: gitk:8688 +#: gitk:8717 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1 идентификатор %s не найден" -#: gitk:8690 +#: gitk:8719 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Редакция %s не найдена в текущем представлении" -#: gitk:8832 gitk:8847 +#: gitk:8861 gitk:8876 msgid "Date" msgstr "Дата" -#: gitk:8835 +#: gitk:8864 msgid "Children" msgstr "Потомки" -#: gitk:8898 +#: gitk:8927 #, tcl-format msgid "Reset %s branch to here" msgstr "Сбросить ветку %s на этот коммит" -#: gitk:8900 +#: gitk:8929 msgid "Detached head: can't reset" msgstr "Коммит не принадлежит ни одной ветке, сбросить невозможно" -#: gitk:9005 gitk:9011 +#: gitk:9034 gitk:9040 msgid "Skipping merge commit " msgstr "Пропускаю коммит-слияние" -#: gitk:9020 gitk:9025 +#: gitk:9049 gitk:9054 msgid "Error getting patch ID for " msgstr "Не удалось получить идентификатор патча для " -#: gitk:9021 gitk:9026 +#: gitk:9050 gitk:9055 msgid " - stopping\n" msgstr " — останов\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 +#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094 msgid "Commit " msgstr "Коммит" -#: gitk:9035 +#: gitk:9064 msgid "" " is the same patch as\n" " " msgstr " такой же патч, как и\n " -#: gitk:9043 +#: gitk:9072 msgid "" " differs from\n" " " msgstr " отличается от\n " -#: gitk:9045 +#: gitk:9074 msgid "" "Diff of commits:\n" "\n" msgstr "Различия коммитов:\n\n" -#: gitk:9057 gitk:9066 +#: gitk:9086 gitk:9095 #, tcl-format msgid " has %s children - stopping\n" msgstr " является %s потомком — останов\n" -#: gitk:9085 +#: gitk:9114 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Произошла ошибка при записи коммита в файл: %s" -#: gitk:9091 +#: gitk:9120 #, tcl-format msgid "Error diffing commits: %s" msgstr "Произошла ошибка при выводе различий коммитов: %s" -#: gitk:9137 +#: gitk:9166 msgid "Top" msgstr "Верх" -#: gitk:9138 +#: gitk:9167 msgid "From" msgstr "От" -#: gitk:9143 +#: gitk:9172 msgid "To" msgstr "До" -#: gitk:9167 +#: gitk:9196 msgid "Generate patch" msgstr "Создать патч" -#: gitk:9169 +#: gitk:9198 msgid "From:" msgstr "От:" -#: gitk:9178 +#: gitk:9207 msgid "To:" msgstr "До:" -#: gitk:9187 +#: gitk:9216 msgid "Reverse" msgstr "В обратном порядке" -#: gitk:9189 gitk:9385 +#: gitk:9218 gitk:9428 msgid "Output file:" msgstr "Файл для сохранения:" -#: gitk:9195 +#: gitk:9224 msgid "Generate" msgstr "Создать" -#: gitk:9233 +#: gitk:9262 msgid "Error creating patch:" msgstr "Ошибка создания патча:" -#: gitk:9256 gitk:9373 gitk:9430 +#: gitk:9285 gitk:9416 gitk:9504 msgid "ID:" msgstr "ID:" -#: gitk:9265 +#: gitk:9294 msgid "Tag name:" msgstr "Имя метки:" -#: gitk:9268 +#: gitk:9297 msgid "Tag message is optional" msgstr "Описание метки указывать не обязательно" -#: gitk:9270 +#: gitk:9299 msgid "Tag message:" msgstr "Описание метки:" -#: gitk:9274 gitk:9439 +#: gitk:9303 gitk:9474 msgid "Create" msgstr "Создать" -#: gitk:9292 +#: gitk:9321 msgid "No tag name specified" msgstr "Не задано имя метки" -#: gitk:9296 +#: gitk:9325 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Метка «%s» уже существует" -#: gitk:9306 +#: gitk:9335 msgid "Error creating tag:" msgstr "Ошибка создания метки:" -#: gitk:9382 +#: gitk:9425 msgid "Command:" msgstr "Команда:" -#: gitk:9390 +#: gitk:9433 msgid "Write" msgstr "Запись" -#: gitk:9408 +#: gitk:9451 msgid "Error writing commit:" msgstr "Произошла ошибка при записи коммита:" -#: gitk:9435 +#: gitk:9473 +msgid "Create branch" +msgstr "Создать ветку" + +#: gitk:9489 +#, tcl-format +msgid "Rename branch %s" +msgstr "Переименовать ветку %s" + +#: gitk:9490 +msgid "Rename" +msgstr "Переименовать" + +#: gitk:9514 msgid "Name:" msgstr "Имя:" -#: gitk:9458 +#: gitk:9538 msgid "Please specify a name for the new branch" msgstr "Укажите имя для новой ветки" -#: gitk:9463 +#: gitk:9543 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Ветка «%s» уже существует. Переписать?" -#: gitk:9530 +#: gitk:9587 +msgid "Please specify a new name for the branch" +msgstr "Укажите имя для новой ветки" + +#: gitk:9650 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "Коммит %s уже включён в ветку %s. Продолжить операцию?" -#: gitk:9535 +#: gitk:9655 msgid "Cherry-picking" -msgstr "Копирование изменений" +msgstr "Копирование коммита" -#: gitk:9544 +#: gitk:9664 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." -msgstr "Отбор лучшего невозможен из-за изменений в файле «%s».\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию." +msgstr "Копирование коммита невозможно из-за изменений в файле «%s».\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию." -#: gitk:9550 +#: gitk:9670 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" msgstr "Копирование изменений невозможно из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?" -#: gitk:9566 gitk:9624 +#: gitk:9686 gitk:9744 msgid "No changes committed" msgstr "Изменения не закоммичены" -#: gitk:9593 +#: gitk:9713 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "Коммит %s не включён в ветку %s. Продолжить операцию?" -#: gitk:9598 +#: gitk:9718 msgid "Reverting" -msgstr "Возврат изменений" +msgstr "Обращение изменений" -#: gitk:9606 +#: gitk:9726 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " "commit, reset or stash your changes and try again." -msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию." +msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию." -#: gitk:9610 +#: gitk:9730 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" msgstr "Возврат изменений невозможен из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?" -#: gitk:9653 +#: gitk:9773 msgid "Confirm reset" msgstr "Подтвердите операцию перехода" -#: gitk:9655 +#: gitk:9775 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Сбросить ветку %s на коммит %s?" -#: gitk:9657 +#: gitk:9777 msgid "Reset type:" msgstr "Тип операции перехода:" -#: gitk:9660 +#: gitk:9780 msgid "Soft: Leave working tree and index untouched" msgstr "Лёгкий: оставить рабочий каталог и индекс неизменными" -#: gitk:9663 +#: gitk:9783 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Смешанный: оставить рабочий каталог неизменным, установить индекс" -#: gitk:9666 +#: gitk:9786 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" msgstr "Жесткий: переписать индекс и рабочий каталог\n(все изменения в рабочем каталоге будут потеряны)" -#: gitk:9683 +#: gitk:9803 msgid "Resetting" msgstr "Сброс" -#: gitk:9743 +#: gitk:9876 +#, tcl-format +msgid "A local branch named %s exists already" +msgstr "Локальная ветка с именем %s уже существует" + +#: gitk:9884 msgid "Checking out" msgstr "Переход" -#: gitk:9796 +#: gitk:9943 msgid "Cannot delete the currently checked-out branch" msgstr "Активная ветка не может быть удалена" -#: gitk:9802 +#: gitk:9949 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" "Really delete branch %s?" msgstr "Коммиты из ветки %s не принадлежат больше никакой другой ветке.\nДействительно удалить ветку %s?" -#: gitk:9833 +#: gitk:9980 #, tcl-format msgid "Tags and heads: %s" msgstr "Метки и ветки: %s" -#: gitk:9850 +#: gitk:9997 msgid "Filter" msgstr "Фильтровать" -#: gitk:10146 +#: gitk:10293 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." msgstr "Ошибка чтения истории проекта; информация о ветках и коммитах вокруг меток (до/после) может быть неполной." -#: gitk:11123 +#: gitk:11270 msgid "Tag" msgstr "Метка" -#: gitk:11127 +#: gitk:11274 msgid "Id" msgstr "Id" -#: gitk:11210 +#: gitk:11357 msgid "Gitk font chooser" msgstr "Шрифт Gitk" -#: gitk:11227 +#: gitk:11374 msgid "B" msgstr "Ж" -#: gitk:11230 +#: gitk:11377 msgid "I" msgstr "К" -#: gitk:11348 +#: gitk:11495 msgid "Commit list display options" msgstr "Параметры показа списка коммитов" -#: gitk:11351 +#: gitk:11498 msgid "Maximum graph width (lines)" msgstr "Макс. ширина графа (строк)" -#: gitk:11355 +#: gitk:11502 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Макс. ширина графа (% ширины панели)" -#: gitk:11358 +#: gitk:11505 msgid "Show local changes" msgstr "Показывать изменения в рабочем каталоге" -#: gitk:11361 +#: gitk:11508 msgid "Auto-select SHA1 (length)" msgstr "Автоматически выделить SHA1 (длинна)" -#: gitk:11365 +#: gitk:11512 msgid "Hide remote refs" msgstr "Скрыть внешние ссылки" -#: gitk:11369 +#: gitk:11516 msgid "Diff display options" msgstr "Параметры показа изменений" -#: gitk:11371 +#: gitk:11518 msgid "Tab spacing" msgstr "Ширина табуляции" -#: gitk:11374 +#: gitk:11521 msgid "Display nearby tags/heads" msgstr "Показывать близкие метки/ветки" -#: gitk:11377 +#: gitk:11524 msgid "Maximum # tags/heads to show" msgstr "Показывать максимальное количество меток/веток" -#: gitk:11380 +#: gitk:11527 msgid "Limit diffs to listed paths" msgstr "Ограничить показ изменений выбранными файлами" -#: gitk:11383 +#: gitk:11530 msgid "Support per-file encodings" msgstr "Поддержка кодировок в отдельных файлах" -#: gitk:11389 gitk:11536 +#: gitk:11536 gitk:11683 msgid "External diff tool" msgstr "Программа для показа изменений" -#: gitk:11390 +#: gitk:11537 msgid "Choose..." msgstr "Выберите..." -#: gitk:11395 +#: gitk:11542 msgid "General options" msgstr "Общие опции" -#: gitk:11398 +#: gitk:11545 msgid "Use themed widgets" msgstr "Использовать стили виджетов" -#: gitk:11400 +#: gitk:11547 msgid "(change requires restart)" msgstr "(изменение потребует перезапуск)" -#: gitk:11402 +#: gitk:11549 msgid "(currently unavailable)" msgstr "(недоступно в данный момент)" -#: gitk:11413 +#: gitk:11560 msgid "Colors: press to choose" msgstr "Цвета: нажмите для выбора" -#: gitk:11416 +#: gitk:11563 msgid "Interface" msgstr "Интерфейс" -#: gitk:11417 +#: gitk:11564 msgid "interface" msgstr "интерфейс" -#: gitk:11420 +#: gitk:11567 msgid "Background" msgstr "Фон" -#: gitk:11421 gitk:11451 +#: gitk:11568 gitk:11598 msgid "background" msgstr "фон" -#: gitk:11424 +#: gitk:11571 msgid "Foreground" msgstr "Передний план" -#: gitk:11425 +#: gitk:11572 msgid "foreground" msgstr "передний план" -#: gitk:11428 +#: gitk:11575 msgid "Diff: old lines" msgstr "Изменения: старый текст" -#: gitk:11429 +#: gitk:11576 msgid "diff old lines" msgstr "старый текст изменения" -#: gitk:11433 +#: gitk:11580 msgid "Diff: new lines" msgstr "Изменения: новый текст" -#: gitk:11434 +#: gitk:11581 msgid "diff new lines" msgstr "новый текст изменения" -#: gitk:11438 +#: gitk:11585 msgid "Diff: hunk header" msgstr "Изменения: заголовок блока" -#: gitk:11440 +#: gitk:11587 msgid "diff hunk header" msgstr "заголовок блока изменений" -#: gitk:11444 +#: gitk:11591 msgid "Marked line bg" msgstr "Фон выбранной строки" -#: gitk:11446 +#: gitk:11593 msgid "marked line background" msgstr "фон выбранной строки" -#: gitk:11450 +#: gitk:11597 msgid "Select bg" msgstr "Выберите фон" -#: gitk:11459 +#: gitk:11606 msgid "Fonts: press to choose" msgstr "Шрифт: нажмите для выбора" -#: gitk:11461 +#: gitk:11608 msgid "Main font" msgstr "Основной шрифт" -#: gitk:11462 +#: gitk:11609 msgid "Diff display font" msgstr "Шрифт показа изменений" -#: gitk:11463 +#: gitk:11610 msgid "User interface font" msgstr "Шрифт интерфейса" -#: gitk:11485 +#: gitk:11632 msgid "Gitk preferences" msgstr "Настройки Gitk" -#: gitk:11494 +#: gitk:11641 msgid "General" msgstr "Общие" -#: gitk:11495 +#: gitk:11642 msgid "Colors" msgstr "Цвета" -#: gitk:11496 +#: gitk:11643 msgid "Fonts" msgstr "Шрифты" -#: gitk:11546 +#: gitk:11693 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: выберите цвет для %s" -#: gitk:12059 +#: gitk:12206 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." msgstr "К сожалению gitk не может работать с этой версий Tcl/Tk.\nТребуется как минимум Tcl/Tk 8.4." -#: gitk:12269 +#: gitk:12416 msgid "Cannot find a git repository here." msgstr "Git-репозитарий не найден в текущем каталоге." -#: gitk:12316 +#: gitk:12463 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Неоднозначный аргумент «%s»: существует как редакция и как имя файла" -#: gitk:12328 +#: gitk:12475 msgid "Bad arguments to gitk:" msgstr "Неправильные аргументы для gitk:" From 29004bbb34a1675f3481efc43283ea997cb2a20c Mon Sep 17 00:00:00 2001 From: Ray Chen Date: Wed, 28 Dec 2016 17:08:45 +0800 Subject: [PATCH 0133/1540] l10n: zh_CN: review for git v2.11.0 l10n Signed-off-by: Ray Chen Signed-off-by: Jiang Xin --- po/zh_CN.po | 63 ++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index c38326c6f106f6..4f5c3ab9aebbc2 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -136,7 +136,7 @@ msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" "POT-Creation-Date: 2016-11-25 22:50+0800\n" -"PO-Revision-Date: 2016-11-25 22:54+0800\n" +"PO-Revision-Date: 2017-01-02 20:11+0800\n" "Last-Translator: Jiang Xin \n" "Language-Team: GitHub \n" "Language: zh_CN\n" @@ -404,7 +404,7 @@ msgstr "缺失 '%s' 的二进制补丁数据" #: apply.c:3098 #, c-format msgid "cannot reverse-apply a binary patch without the reverse hunk to '%s'" -msgstr "不能反向应用一个没有至 '%s' 的反向数据块的二进制补丁" +msgstr "不能反向应用一个缺少到 '%s' 的反向数据块的二进制补丁" #: apply.c:3144 #, c-format @@ -415,12 +415,12 @@ msgstr "不能在 '%s' 上应用没有完整索引行的二进制补丁" #, c-format msgid "" "the patch applies to '%s' (%s), which does not match the current contents." -msgstr "补丁引用到 '%s' (%s),但是和当前内容不匹配。" +msgstr "补丁应用到 '%s'(%s),但是和当前内容不匹配。" #: apply.c:3162 #, c-format msgid "the patch applies to an empty '%s' but it is not empty" -msgstr "补丁应用到空文件 '%s',但是它并不空" +msgstr "补丁应用到空文件 '%s',但其并非空文件" #: apply.c:3180 #, c-format @@ -563,7 +563,7 @@ msgstr "子模组 %s 的 sha1 信息缺失或无效" #: apply.c:4082 #, c-format msgid "mode change for %s, which is not in current HEAD" -msgstr "%s 的属性改变,但它不再当前 HEAD 中" +msgstr "%s 的模式被改变,但它不在当前 HEAD 中" #: apply.c:4085 #, c-format @@ -1309,9 +1309,9 @@ msgid "" "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n" msgstr "" -"警告:提交说明不符合 UTF-8 字符集。\n" +"警告:提交说明不符合 UTF-8 字符编码。\n" "您可以通过修补提交来改正提交说明,或者将配置变量 i18n.commitencoding\n" -"设置为您项目所用的字符集。\n" +"设置为您项目所用的字符编码。\n" #: compat/obstack.c:406 compat/obstack.c:408 msgid "memory exhausted" @@ -1440,7 +1440,7 @@ msgstr "不能取消设置 '%s'" #: connect.c:49 msgid "The remote end hung up upon initial contact" -msgstr "远端在初始连接时即挂断" +msgstr "远端在连接发起时即挂断" #: connect.c:51 msgid "" @@ -1644,7 +1644,7 @@ msgstr "因为文件太多,只在修改的路径中查找拷贝。" #, c-format msgid "" "you may want to set your %s variable to at least %d and retry the command." -msgstr "您可能想要将您的 %s 变量至少设置为 %d 并重复此命令。" +msgstr "您可能想要将变量 %s 设置为至少 %d 并再次执行此命令。" #: dir.c:1866 msgid "failed to get kernel name and information" @@ -1732,7 +1732,7 @@ msgstr "标记 %s 为完成" #: fetch-pack.c:697 #, c-format msgid "already have %s (%s)" -msgstr "已经有 %s (%s)" +msgstr "已经有 %s(%s)" #: fetch-pack.c:735 msgid "fetch-pack: unable to fork off sideband demultiplexer" @@ -2215,9 +2215,9 @@ msgid "" "Please, use 'git notes merge --commit' or 'git notes merge --abort' to " "commit/abort the previous merge before you start a new notes merge." msgstr "" -"您尚未结束您前一次注释合并(存在 %s)。\n" -"请您在开始一个新的注释合并之前,使用 'git notes merge --commit' 或者 'git " -"notes merge --abort' 来提交/终止前一次合并。" +"您的前一次注释合并尚未结束(存在 %s)。\n" +"在开始一个新的注释合并之前,请使用 'git notes merge --commit' 或者 " +"'git notes merge --abort' 来提交/终止前一次合并。" #: notes-merge.c:280 #, c-format @@ -2340,7 +2340,7 @@ msgid "" "empty strings as pathspecs will be made invalid in upcoming releases. please " "use . instead if you meant to match all paths" msgstr "" -"在即将到来的版本,不能再使用空字符串作为路径表达式。如果要匹配所有路径,\n" +"在下一个版本中,使用空字符串作为路径规格将被视作非法。如果要匹配所有路径,\n" "请代之以 ." #: pathspec.c:440 @@ -2771,7 +2771,7 @@ msgstr "不能解析 HEAD 提交\n" #: sequencer.c:438 msgid "unable to update cache tree\n" -msgstr "不能更新缓存\n" +msgstr "不能更新缓存树\n" #: sequencer.c:483 #, c-format @@ -2789,11 +2789,11 @@ msgid "" "\n" " git rebase --continue\n" msgstr "" -"您已暂存了工作区的修改。如果这些修改要压缩到前一个提交,执行:\n" +"您的工作区中存在已暂存的修改\n如果这些修改需要被并入前一个提交,执行:\n" "\n" " git commit --amend %s\n" "\n" -"如果这些变更要形成一个新提交,执行:\n" +"如果这些修改要形成一个新提交,执行:\n" "\n" " git commit %s\n" "\n" @@ -2883,7 +2883,7 @@ msgstr "不能读取 '%s'。" #: sequencer.c:972 #, c-format msgid "unusable instruction sheet: '%s'" -msgstr "无用的指令清单:'%s'" +msgstr "不可用的指令清单:'%s'" #: sequencer.c:983 msgid "cannot cherry-pick during a revert." @@ -3088,7 +3088,7 @@ msgstr "偏移量越过了 %s 的包索引的结尾(被截断的索引?)" #: sha1_name.c:407 #, c-format msgid "short SHA1 %s is ambiguous" -msgstr "歧义的短 SHA1 %s" +msgstr "短 SHA1 %s 存在歧义" #: sha1_name.c:418 msgid "The candidates are:" @@ -3260,7 +3260,7 @@ msgstr "太短的树对象" #: tree-walk.c:37 msgid "malformed mode in tree entry" -msgstr "树对象中的条目属性错误" +msgstr "树对象中的条目模式错误" #: tree-walk.c:41 msgid "empty filename in tree entry" @@ -4043,7 +4043,7 @@ msgstr "不能%s:您有未暂存的变更。" #: wt-status.c:2276 msgid "additionally, your index contains uncommitted changes." -msgstr "而且您的索引中包含未提交的变更。" +msgstr "另外,您的索引中包含未提交的变更。" #: wt-status.c:2278 #, c-format @@ -4778,7 +4778,7 @@ msgstr "在 %2$s 中无此路径 %1$s" #: builtin/blame.c:2854 #, c-format msgid "cannot read blob %s for path %s" -msgstr "不能为路径 %2$s 读取对象 %1$s" +msgstr "不能为路径 %2$s 读取数据对象 %1$s" #: builtin/blame.c:2873 #, c-format @@ -7749,7 +7749,7 @@ msgstr "手工维护参见 \"git help gc\"。\n" msgid "" "gc is already running on machine '%s' pid % (use --force if not)" msgstr "" -"已经有一个 gc 正运行在机器 '%s' pid % (如果不是,使用 --force)" +"已经有一个 gc 正运行在机器 '%s' pid %(如果不是,使用 --force)" #: builtin/gc.c:441 msgid "" @@ -9240,7 +9240,7 @@ msgstr "坏的 branch.%s.mergeoptions 字符串:%s" #: builtin/merge.c:652 msgid "Not handling anything other than two heads merge." -msgstr "不能处理两个头合并之外的任何操作。" +msgstr "未处理两个头合并之外的任何操作。" #: builtin/merge.c:666 #, c-format @@ -10904,10 +10904,10 @@ msgid "" "To squelch this message, you can set it to 'refuse'." msgstr "" "默认禁止删除当前分支,因为下一次 'git clone' 将不会检出任何文件,\n" -"导致混淆。\n" +"导致困惑。\n" "\n" "您可以在远程仓库中设置 'receive.denyDeleteCurrent' 配置变量为\n" -"'warn' 或 'ignore' 以允许删除当前分支,显示或者不显示警告。\n" +"'warn'(显示警告信息)或 'ignore'(忽略警告信息)以允许删除当前分支。\n" "\n" "若要屏蔽此信息,您可以设置它为 'refuse'。" @@ -12124,7 +12124,7 @@ msgstr "'%s' 不是一个有效的引用。" #: builtin/show-branch.c:836 #, c-format msgid "cannot find commit %s (%s)" -msgstr "不能找到提交 %s (%s)" +msgstr "不能找到提交 %s(%s)" #: builtin/show-ref.c:10 msgid "" @@ -12286,7 +12286,7 @@ msgstr "浅克隆的深度" #: builtin/submodule--helper.c:608 builtin/submodule--helper.c:964 msgid "force cloning progress" -msgstr "显示克隆进度" +msgstr "强制显示克隆进度" #: builtin/submodule--helper.c:613 msgid "" @@ -12964,7 +12964,7 @@ msgstr "不能创建目录 '%s'" #: builtin/worktree.c:272 #, c-format msgid "Preparing %s (identifier %s)" -msgstr "准备 %s (标识符 %s)" +msgstr "准备 %s(标识符 %s)" #: builtin/worktree.c:323 msgid "checkout even if already checked out in other worktree" @@ -13058,7 +13058,7 @@ msgid "" "\n" "\tchmod 0700 %s" msgstr "" -"您 socket 目录权限过于放松,其他用户可能会读取您缓存的认证信息。考虑执行:\n" +"您的 socket 目录权限过于宽松,其他用户可能会读取您缓存的认证信息。考虑执行:\n" "\n" "\tchmod 0700 %s" @@ -14280,8 +14280,7 @@ msgid "" "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --" "continue'." msgstr "" -"您可以用命令 'git rebase --edit-todo' 修正然后执行命令 'git rebase --" -"continue'。" +"您可以用 'git rebase --edit-todo' 修正问题然后执行 'git rebase --continue'。" #: git-rebase--interactive.sh:1045 msgid "Or you can abort the rebase with 'git rebase --abort'." From 16996772afbb46e5d46c60c841f16bf403de40f5 Mon Sep 17 00:00:00 2001 From: Jordi Mas Date: Tue, 3 Jan 2017 23:28:46 +0100 Subject: [PATCH 0134/1540] l10n: fixes to Catalan translation Signed-off-by: Jordi Mas --- po/ca.po | 597 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 308 insertions(+), 289 deletions(-) diff --git a/po/ca.po b/po/ca.po index 57d0e2faa8780b..0158ea410d919c 100644 --- a/po/ca.po +++ b/po/ca.po @@ -1,8 +1,28 @@ # Catalan translations for Git. # Copyright (C) 2014 Alex Henrie # This file is distributed under the same license as the Git package. -# Alex Henrie , 2014. +# Alex Henrie , 2014-2016. +# Jordi Mas i Hernàndez , 2016-2017 +# +# Terminologia # +# broken -> malmès +# deprecated -> en desús +# skip -> ometre +# token -> testimoni +# +# Alguns termes que són comandes específiques del git i d'àmbit molt tècnic +# hem decidit no traduir-los per facilitar-ne la compressió a l'usuari i perquè +# no tenen una transcendència al gran públic. Es tracta de casos similars +# a «ping» en l'àmbit de xarxes. +# +# Termes que mantenim en anglès: +# +# blame -> «blame» +# cherry pick -> «cherry pick» +# rebase -> «rebase» +# + msgid "" msgstr "" "Project-Id-Version: Git\n" @@ -25,7 +45,7 @@ msgstr "pista: %.*s\n" #: advice.c:83 msgid "Cherry-picking is not possible because you have unmerged files." -msgstr "Recollir cireres no és possible perquè teniu fitxers no fusionats." +msgstr "Fer «cherry pick» no és possible perquè teniu fitxers no fusionats." #: advice.c:85 msgid "Committing is not possible because you have unmerged files." @@ -37,7 +57,7 @@ msgstr "Fusionar no és possible perquè teniu fitxers no fusionats." #: advice.c:89 msgid "Pulling is not possible because you have unmerged files." -msgstr "Baixar no es possible perquè teniu fitxers no fusionats." +msgstr "Baixar no és possible perquè teniu fitxers no fusionats." #: advice.c:91 msgid "Reverting is not possible because you have unmerged files." @@ -46,7 +66,7 @@ msgstr "Revertir no és possible perquè teniu fitxers no fusionats." #: advice.c:93 #, c-format msgid "It is not possible to %s because you have unmerged files." -msgstr "No es possible %s perquè teniu fitxers no fusionats." +msgstr "No és possible %s perquè teniu fitxers no fusionats." #: advice.c:101 msgid "" @@ -91,7 +111,7 @@ msgstr "" "Avís: s'està agafant '%s'.\n" "\n" "Esteu en un estat de 'HEAD separat'. Podeu mirar al voltant, fer canvis\n" -"experimentals i cometre-los i podeu descartar qualsevulla comissió que feu\n" +"experimentals i cometre-los i podeu descartar qualsevol comissió que feu\n" "en aquest estat sense impactar cap branca realitzant un altre agafament.\n" "\n" "Si voleu crear una branca nova per a conservar les comissions que creeu,\n" @@ -108,7 +128,7 @@ msgstr "opció d'espai en blanc '%s' no reconeguda" #: apply.c:73 #, c-format msgid "unrecognized whitespace ignore option '%s'" -msgstr "opció d'ignoral d'espai en blanc '%s' no reconeguda" +msgstr "opció d'ignora l'espai en blanc '%s' no reconeguda" #: apply.c:125 msgid "--reject and --3way cannot be used together." @@ -310,7 +330,7 @@ msgstr "el pedaç s'aplica a un '%s' buit però no és buit" #: apply.c:3180 #, c-format msgid "the necessary postimage %s for '%s' cannot be read" -msgstr "no es pot llegir la postimatge necessari %s per a '%s'" +msgstr "no es pot llegir la postimatge %s necessària per a '%s'" #: apply.c:3193 #, c-format @@ -336,7 +356,7 @@ msgstr "no es pot agafar %s" #: apply.c:3390 apply.c:3401 apply.c:3447 setup.c:248 #, c-format msgid "failed to read %s" -msgstr "s'ha fallat en llegir %s" +msgstr "s'ha produït un error en llegir %s" #: apply.c:3398 #, c-format @@ -376,7 +396,7 @@ msgstr "no es poden llegir els continguts actuals de '%s'" #: apply.c:3589 #, c-format msgid "Failed to fall back on three-way merge...\n" -msgstr "S'ha fallat en retrocedir a una fusió de 3 vies...\n" +msgstr "S'ha produït un error en retrocedir a una fusió de 3 vies...\n" #: apply.c:3603 #, c-format @@ -465,7 +485,7 @@ msgstr "make_cache_entry ha fallat per al camí '%s'" #: apply.c:4094 #, c-format msgid "could not add %s to temporary index" -msgstr "no s'ha pogut afegir %s a l'index temporal" +msgstr "no s'ha pogut afegir %s a l'índex temporal" #: apply.c:4104 #, c-format @@ -502,7 +522,7 @@ msgstr "no s'ha pogut afegir una entrada de cau per a %s" #: apply.c:4338 #, c-format msgid "failed to write to '%s'" -msgstr "s'ha fallat en escriure a '%s'" +msgstr "no s'ha pogut escriure a '%s'" #: apply.c:4342 #, c-format @@ -528,7 +548,7 @@ msgstr "error intern" msgid "Applying patch %%s with %d reject..." msgid_plural "Applying patch %%s with %d rejects..." msgstr[0] "S'està aplicant el pedaç %%s amb %d rebuig..." -msgstr[1] "S'està aplicant el pedaç %%s amb %d rebuitjos..." +msgstr[1] "S'està aplicant el pedaç %%s amb %d rebutjos..." #: apply.c:4532 #, c-format @@ -553,7 +573,7 @@ msgstr "S'ha rebutjat el tros #%d." #: apply.c:4668 #, c-format msgid "Skipped patch '%s'." -msgstr "S'ha saltat el pedaç '%s'." +msgstr "S'ha omès el pedaç '%s'." #: apply.c:4676 msgid "unrecognized input" @@ -587,9 +607,9 @@ msgstr[1] "%d línies afegeixen errors d'espai en blanc." msgid "%d line applied after fixing whitespace errors." msgid_plural "%d lines applied after fixing whitespace errors." msgstr[0] "" -"S'ha aplicat %d línia desprès d'arreglar els errors d'espai en blanc." +"S'ha aplicat %d línia després d'arreglar els errors d'espai en blanc." msgstr[1] "" -"S'han aplicat %d línies desprès d'arreglar els errors d'espai en blanc." +"S'han aplicat %d línies després d'arreglar els errors d'espai en blanc." #: apply.c:4888 builtin/add.c:463 builtin/mv.c:286 builtin/rm.c:431 msgid "Unable to write new index file" @@ -697,7 +717,7 @@ msgstr "no esperis almenys una línia de context" #: apply.c:4971 msgid "leave the rejected hunks in corresponding *.rej files" -msgstr "deixa els trossos rebutjats en fitxers *.reg corresponents" +msgstr "deixa els trossos rebutjats en fitxers *.rej corresponents" #: apply.c:4973 msgid "allow overlapping hunks" @@ -799,7 +819,7 @@ msgstr "comprimeix millor" #: archive.c:449 msgid "list supported archive formats" -msgstr "allista els formats d'arxiu admesos" +msgstr "llista els formats d'arxiu admesos" #: archive.c:451 builtin/archive.c:90 builtin/clone.c:85 builtin/clone.c:88 #: builtin/submodule--helper.c:601 builtin/submodule--helper.c:953 @@ -856,7 +876,7 @@ msgstr "No s'ha pogut obrir el fitxer '%s'" #: bisect.c:446 #, c-format msgid "Badly quoted content in file '%s': %s" -msgstr "Comentari amb cometes dolentes en el fitxer '%s': %s" +msgstr "Comentari amb cometes errònies en el fitxer '%s': %s" #: bisect.c:655 #, c-format @@ -874,7 +894,7 @@ msgid "" "The merge base %s is bad.\n" "This means the bug has been fixed between %s and [%s].\n" msgstr "" -"La base de fusió %s és dolenta.\n" +"La base de fusió %s és errònia.\n" "Això vol dir que el defecte s'ha arreglat entre %s i [%s].\n" #: bisect.c:737 @@ -902,7 +922,7 @@ msgid "" "git bisect cannot work properly in this case.\n" "Maybe you mistook %s and %s revs?\n" msgstr "" -"Unes revisions %s no són els avantpassats de la revisió %s.\n" +"Algunes revisions %s no són els avantpassats de la revisió %s.\n" "git bisect no pot funcionar correctament en aquest cas.\n" "Potser heu confós les revisions %s i %s?\n" @@ -913,7 +933,7 @@ msgid "" "So we cannot be sure the first %s commit is between %s and %s.\n" "We continue anyway." msgstr "" -"s'ha de saltar la base de fusió entre %s i [%s].\n" +"s'ha d'ometre la base de fusió entre %s i [%s].\n" "Llavors, no podem estar segurs de que la primera comissió %s sigui entre %s " "i %s.\n" "Continuem de totes maneres." @@ -953,7 +973,7 @@ msgid "" "No testable commit found.\n" "Maybe you started with bad path parameters?\n" msgstr "" -"No s'ha trobat cap comissió provable.\n" +"No s'ha trobat cap comissió probable.\n" "Potser heu començat amb paràmetres de camí dolents?\n" #: bisect.c:994 @@ -1219,8 +1239,8 @@ msgid "" "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n" msgstr "" -"Advertència: el missatge de comissió no conformava a UTF-8.\n" -"Potser voleu esemenar-lo després de corregir el missatge, o establir\n" +"Advertència: el missatge de comissió no és compatible amb UTF-8.\n" +"Potser voleu esmenar-lo després de corregir el missatge, o establir\n" "la variable de configuració i18n.commitencoding a la codificació que\n" "usi el vostre projecte.\n" @@ -1231,32 +1251,32 @@ msgstr "memòria esgotada" #: config.c:516 #, c-format msgid "bad config line %d in blob %s" -msgstr "línia de configuració dolenta %d en el blob %s" +msgstr "línia de configuració %d errònia en el blob %s" #: config.c:520 #, c-format msgid "bad config line %d in file %s" -msgstr "línia de configuració dolenta %d en el fitxer %s" +msgstr "línia de configuració %d errònia en el fitxer %s" #: config.c:524 #, c-format msgid "bad config line %d in standard input" -msgstr "línia de configuració dolenta %d en l'entrada estàndard" +msgstr "línia de configuració %d errònia en l'entrada estàndard" #: config.c:528 #, c-format msgid "bad config line %d in submodule-blob %s" -msgstr "línia de configuració dolenta %d en el blob de submòdul %s" +msgstr "línia de configuració %d errònia en el blob de submòdul %s" #: config.c:532 #, c-format msgid "bad config line %d in command line %s" -msgstr "línia de configuració dolenta %d en la línia d'ordres %s" +msgstr "línia de configuració %d errònia en la línia d'ordres %s" #: config.c:536 #, c-format msgid "bad config line %d in %s" -msgstr "línia de configuració dolenta %d en %s" +msgstr "línia de configuració %d errònia en %s" #: config.c:655 msgid "out of range" @@ -1269,36 +1289,36 @@ msgstr "unitat no vàlida" #: config.c:661 #, c-format msgid "bad numeric config value '%s' for '%s': %s" -msgstr "valor de configuració numèric dolent '%s' per '%s': %s" +msgstr "valor de configuració numèric erroni '%s' per '%s': %s" #: config.c:666 #, c-format msgid "bad numeric config value '%s' for '%s' in blob %s: %s" -msgstr "valor de configuració numèric dolent '%s' per '%s' en el blob %s: %s" +msgstr "valor de configuració numèric erroni '%s' per '%s' en el blob %s: %s" #: config.c:669 #, c-format msgid "bad numeric config value '%s' for '%s' in file %s: %s" -msgstr "valor de configuració numèric dolent '%s' per '%s' en el fitxer %s: %s" +msgstr "valor de configuració numèric '%s' erroni per '%s' en el fitxer %s: %s" #: config.c:672 #, c-format msgid "bad numeric config value '%s' for '%s' in standard input: %s" msgstr "" -"valor de configuració numèric dolent '%s' per '%s' en l'entrada estàndard: %s" +"valor de configuració numèric '%s' erroni per '%s' en l'entrada estàndard: %s" #: config.c:675 #, c-format msgid "bad numeric config value '%s' for '%s' in submodule-blob %s: %s" msgstr "" -"valor de configuració numèric dolent '%s' per '%s' en el blob de submòdul " +"valor de configuració numèric '%s' erroni' per '%s' en el blob de submòdul " "%s: %s" #: config.c:678 #, c-format msgid "bad numeric config value '%s' for '%s' in command line %s: %s" msgstr "" -"valor de configuració numèric dolent '%s' per '%s' en la línia d'ordres %s: " +"valor de configuració numèric '%s' erroni per '%s' en la línia d'ordres %s: " "%s" #: config.c:681 @@ -1309,7 +1329,7 @@ msgstr "valor de configuració numèric dolent '%s' per '%s' en %s: %s" #: config.c:768 #, c-format msgid "failed to expand user dir in: '%s'" -msgstr "s'ha fallat en expandir el directori d'usuari en: '%s'" +msgstr "s'ha produït un error en expandir el directori d'usuari en: '%s'" #: config.c:852 config.c:863 #, c-format @@ -1337,7 +1357,7 @@ msgstr "no s'ha pogut analitzar '%s' de la configuració de la línia d'ordres" #: config.c:1718 #, c-format msgid "bad config variable '%s' in file '%s' at line %d" -msgstr "variable de configuració dolenta '%s' en el fitxer '%s' a la línia %d" +msgstr "variable de configuració '%s' errònia en el fitxer '%s' a la línia %d" #: config.c:1777 #, c-format @@ -1384,7 +1404,7 @@ msgstr "escriptura fallada al rev-list" #: connected.c:102 msgid "failed to close rev-list's stdin" -msgstr "s'ha fallat en tancar l'stdin del rev-list" +msgstr "s'ha produït un error en tancar l'stdin del rev-list" #: convert.c:201 #, c-format @@ -1487,7 +1507,7 @@ msgstr[1] "fa %lu anys" #: diffcore-order.c:24 #, c-format msgid "failed to read orderfile '%s'" -msgstr "s'ha fallat en llegir el fitxer d'ordres '%s'" +msgstr "s'ha produït un error en llegir el fitxer d'ordres '%s'" #: diffcore-rename.c:536 msgid "Performing inexact rename detection" @@ -1501,7 +1521,7 @@ msgstr "l'opció '%s' requereix un valor" #: diff.c:124 #, c-format msgid " Failed to parse dirstat cut-off percentage '%s'\n" -msgstr " S'ha fallat en analitzar el percentatge limitant de dirstat '%s'\n" +msgstr " S'ha produït un error en analitzar el percentatge limitant de dirstat '%s'\n" #: diff.c:129 #, c-format @@ -1542,17 +1562,17 @@ msgid "" "Failed to parse --dirstat/-X option parameter:\n" "%s" msgstr "" -"S'ha fallat en analitzar el paràmetre d'opció de --dirstat/-X:\n" +"S'ha produït un error en analitzar el paràmetre d'opció de --dirstat/-X:\n" "%s" #: diff.c:3679 #, c-format msgid "Failed to parse --submodule option parameter: '%s'" -msgstr "S'ha fallat en analitzar el paràmetre d'opció de --submodule: '%s'" +msgstr "S'ha produït un error en analitzar el paràmetre d'opció de --submodule: '%s'" #: diff.c:4700 msgid "inexact rename detection was skipped due to too many files." -msgstr "s'ha saltat la detecció de canvi de nom a causa de massa fitxers." +msgstr "s'ha omès la detecció de canvi de nom a causa de massa fitxers." #: diff.c:4703 msgid "only found copies from modified paths due to too many files." @@ -1569,7 +1589,7 @@ msgstr "" #: dir.c:1866 msgid "failed to get kernel name and information" -msgstr "s'ha fallat en obtenir el nombre i la informació del nucli" +msgstr "s'ha produït un error en obtenir el nombre i la informació del nucli" #: dir.c:1985 msgid "Untracked cache is disabled on this system or location." @@ -1616,7 +1636,7 @@ msgstr "error en objecte: %s" #: fetch-pack.c:394 #, c-format msgid "no shallow found: %s" -msgstr "no s'ha trobat cap superficial: %s" +msgstr "no s'ha trobat cap shallow: %s" #: fetch-pack.c:397 #, c-format @@ -1662,7 +1682,7 @@ msgstr "fetch-pack: no s'ha pogut bifurcar del demultiplexor de banda lateral" #: fetch-pack.c:743 msgid "protocol error: bad pack header" -msgstr "error de protocol: capçalera de paquet dolent" +msgstr "error de protocol: capçalera de paquet errònia" #: fetch-pack.c:799 #, c-format @@ -1754,7 +1774,7 @@ msgstr "no s'ha pogut crear el fitxer temporal" #: gpg-interface.c:217 #, c-format msgid "failed writing detached signature to '%s'" -msgstr "s'ha fallat en escriure la signatura separada a '%s'" +msgstr "s'ha produït un error en escriure la signatura separada a '%s'" #: grep.c:1782 #, c-format @@ -1764,7 +1784,7 @@ msgstr "'%s': no s'ha pogut llegir %s" #: grep.c:1799 builtin/clone.c:381 builtin/diff.c:84 builtin/rm.c:155 #, c-format msgid "failed to stat '%s'" -msgstr "s'ha fallat en fer stat a '%s'" +msgstr "s'ha produït un error en fer stat a '%s'" #: grep.c:1810 #, c-format @@ -1791,7 +1811,7 @@ msgid "" "able to execute it. Maybe git-%s is broken?" msgstr "" "'%s' sembla una ordre de git, però no hem pogut\n" -"executar-la. Pot ser que git-%s estigui estropejat?" +"executar-la. Pot ser que git-%s estigui malmès?" #: help.c:361 msgid "Uh oh. Your system reports no Git commands at all." @@ -1855,10 +1875,10 @@ msgstr "" "\n" "Executeu\n" "\n" -" git config --global user.email \"vós@example.com\"\n" -" git config --global user.name \"El Vostre Nom\"\n" +" git config --global user.email \"usuari@domini.com\"\n" +" git config --global user.name \"El vostre nom\"\n" "\n" -"per a establir la identitat predeterminat del vostre compte.\n" +"per a establir la identitat predeterminada del vostre compte.\n" "Ometeu --global per a establir la identitat només en aquest dipòsit.\n" #: lockfile.c:152 @@ -1877,7 +1897,7 @@ msgstr "" "Sembla que un altre procés de git s'està executant en aquest\n" "dipòsit, per exemple, un editor obert per 'git commit'. Si us\n" "plau, assegureu-vos que tots els processos s'hagin terminat i\n" -"llavors trobeu de nou. Si encara falla, potser que un procés de\n" +"llavors proveu de nou. Si encara falla, pot ser que un procés de\n" "git ha tingut una pana:\n" "elimineu el fitxer manualment per a continuar." @@ -1888,7 +1908,7 @@ msgstr "No s'ha pogut crear '%s.lock': %s" #: merge.c:41 msgid "failed to read the cache" -msgstr "s'ha fallat en llegir la memòria cau" +msgstr "s'ha produït un error en llegir la memòria cau" #: merge.c:96 builtin/am.c:2000 builtin/am.c:2035 builtin/checkout.c:374 #: builtin/checkout.c:588 builtin/clone.c:731 @@ -1897,7 +1917,7 @@ msgstr "no s'ha pogut escriure un fitxer d'índex nou" #: merge-recursive.c:209 msgid "(bad commit)\n" -msgstr "(comissió dolenta)\n" +msgstr "(comissió errònia)\n" #: merge-recursive.c:231 #, c-format @@ -1911,7 +1931,7 @@ msgstr "error en construir arbres" #: merge-recursive.c:720 #, c-format msgid "failed to create path '%s'%s" -msgstr "s'ha fallat en crear el camí '%s'%s" +msgstr "s'ha produït un error en crear el camí '%s'%s" #: merge-recursive.c:731 #, c-format @@ -1940,12 +1960,12 @@ msgstr "blob esperat per a %s '%s'" #: merge-recursive.c:822 #, c-format msgid "failed to open '%s': %s" -msgstr "s'ha fallat en obrir '%s': %s" +msgstr "s'ha produït un error en obrir '%s': %s" #: merge-recursive.c:833 #, c-format msgid "failed to symlink '%s': %s" -msgstr "s'ha fallat en fer l'enllaç simbòlic '%s': %s" +msgstr "s'ha produït un error en fer l'enllaç simbòlic '%s': %s" #: merge-recursive.c:838 #, c-format @@ -1954,7 +1974,7 @@ msgstr "no se sap què fer amb %06o %s '%s'" #: merge-recursive.c:978 msgid "Failed to execute internal merge" -msgstr "S'ha fallat en executar la fusió interna" +msgstr "S'ha produït un error en executar la fusió interna" #: merge-recursive.c:982 #, c-format @@ -2063,7 +2083,7 @@ msgstr "afegiment/afegiment" #: merge-recursive.c:1718 #, c-format msgid "Skipped %s (merged same as existing)" -msgstr "S'ha saltat %s (el fusionat és igual a l'existent)" +msgstr "S'ha omès %s (el fusionat és igual a l'existent)" #: merge-recursive.c:1732 #, c-format @@ -2144,7 +2164,7 @@ msgid "" "Please, use 'git notes merge --commit' or 'git notes merge --abort' to " "commit/abort the previous merge before you start a new notes merge." msgstr "" -"No heu conclòs la vostra fusió de notes prèvia (%s existeix).\n" +"No heu acabat la vostra fusió de notes prèvia (%s existeix).\n" "Si us plau, useu 'git notes merge --commit' o 'git notes merge --abort' per " "a cometre/avortar la fusió prèvia abans de començar una fusió de notes nova." @@ -2160,7 +2180,7 @@ msgstr "No es pot cometre un arbre de notes no inicialitzat / no referenciat" #: notes-utils.c:100 #, c-format msgid "Bad notes.rewriteMode value: '%s'" -msgstr "Valor de notes.rewriteMode dolent: '%s'" +msgstr "Valor de notes.rewriteMode erroni: '%s'" #: notes-utils.c:110 #, c-format @@ -2172,7 +2192,7 @@ msgstr "S'està refusant reescriure les notes en %s (fora de refs/notes/)" #: notes-utils.c:137 #, c-format msgid "Bad %s value: '%s'" -msgstr "Valor dolent de %s: '%s'" +msgstr "Valor erroni de %s: '%s'" #: object.c:242 #, c-format @@ -2274,7 +2294,7 @@ msgid "" "use . instead if you meant to match all paths" msgstr "" "es faran no vàlides les cadenes buides com especificacions de camí en " -"versions futures. si us plau, useu . en lloc d'això si volíeu coincidir amb " +"versions futures. Si us plau, useu . en lloc d'això si volíeu coincidir amb " "tots els camins" #: pathspec.c:440 @@ -2360,7 +2380,7 @@ msgstr "%%(subject) no accepta paràmetres" #: ref-filter.c:101 #, c-format msgid "positive value expected contents:lines=%s" -msgstr "valor positiu esperat contents:lines=%s" +msgstr "valor positiu esperat conté:lines=%s" #: ref-filter.c:103 #, c-format @@ -2449,7 +2469,7 @@ msgstr "objecte mal format a '%s'" #: ref-filter.c:1373 #, c-format msgid "ignoring ref with broken name %s" -msgstr "s'està ignorant la referència amb nom trencat %s" +msgstr "s'està ignorant la referència amb nom malmès %s" #: ref-filter.c:1378 #, c-format @@ -2469,7 +2489,7 @@ msgstr "nom d'objecte %s mal format" #: remote.c:746 #, c-format msgid "Cannot fetch both %s and %s to %s" -msgstr "No es pot obtenir ambdós %s i %s a %s" +msgstr "No es poden obtenir ambdós %s i %s a %s" #: remote.c:750 #, c-format @@ -2590,7 +2610,7 @@ msgstr " (useu \"git pull\" per a fusionar la branca remota a la vostra)\n" #: revision.c:2158 msgid "your current branch appears to be broken" -msgstr "la vostra branca actual sembla trencada" +msgstr "la vostra branca actual sembla malmesa" #: revision.c:2161 #, c-format @@ -2603,7 +2623,7 @@ msgstr "--first-parent és incompatible amb --bisect" #: run-command.c:106 msgid "open /dev/null failed" -msgstr "s'ha fallat en obrir /dev/null" +msgstr "s'ha produït un error en obrir /dev/null" #: run-command.c:108 #, c-format @@ -2612,7 +2632,7 @@ msgstr "dup2(%d,%d) ha fallat" #: send-pack.c:297 msgid "failed to sign the push certificate" -msgstr "s'ha fallat en signar el certificat de pujada" +msgstr "s'ha produït un error en signar el certificat de pujada" #: send-pack.c:410 msgid "the receiving end does not support --signed push" @@ -2640,7 +2660,7 @@ msgstr "revertir" #: sequencer.c:171 msgid "cherry-pick" -msgstr "recollir cireres" +msgstr "cherry-pick" #: sequencer.c:228 msgid "" @@ -2678,7 +2698,7 @@ msgstr "no s'ha pogut escriure el terminador de línia a '%s'" #: sequencer.c:255 sequencer.c:1130 sequencer.c:1216 #, c-format msgid "failed to finalize '%s'." -msgstr "s'ha fallat en finalitzar '%s'." +msgstr "s'ha produït un error en finalitzar '%s'." #: sequencer.c:279 builtin/am.c:259 builtin/commit.c:749 builtin/merge.c:1032 #, c-format @@ -2801,12 +2821,12 @@ msgstr "conjunt de comissions buit passat" #: sequencer.c:843 #, c-format msgid "git %s: failed to read the index" -msgstr "git %s: s'ha fallat en llegir l'índex" +msgstr "git %s: s'ha produït un error en llegir l'índex" #: sequencer.c:850 #, c-format msgid "git %s: failed to refresh the index" -msgstr "git %s: s'ha fallat en actualitzar l'índex" +msgstr "git %s: s'ha produït un error en actualitzar l'índex" #: sequencer.c:944 #, c-format @@ -2829,11 +2849,11 @@ msgstr "full d'instruccions inusable: '%s'" #: sequencer.c:983 msgid "cannot cherry-pick during a revert." -msgstr "no es pot recollir cireres durant una reversió." +msgstr "no es pot fer «cherry pick» durant una reversió." #: sequencer.c:985 msgid "cannot revert during a cherry-pick." -msgstr "no es pot revertir durant un recull de cireres." +msgstr "no es pot revertir durant un «cherry pick»." #: sequencer.c:1028 #, c-format @@ -2852,7 +2872,7 @@ msgstr "full d'opcions mal format: '%s'" #: sequencer.c:1101 msgid "a cherry-pick or revert is already in progress" -msgstr "un recull de cireres o una reversió ja està en curs" +msgstr "un «cherry pick» o una reversió ja està en curs" #: sequencer.c:1102 msgid "try \"git cherry-pick (--continue | --quit | --abort)\"" @@ -2869,7 +2889,7 @@ msgstr "no s'ha pogut bloquejar HEAD" #: sequencer.c:1151 sequencer.c:1289 msgid "no cherry-pick or revert in progress" -msgstr "ni hi ha cap recull de cireres ni cap reversió en curs" +msgstr "ni hi ha cap «cherry pick» ni cap reversió en curs" #: sequencer.c:1153 msgid "cannot resolve HEAD" @@ -2896,12 +2916,12 @@ msgstr "final de fitxer inesperat" #: sequencer.c:1184 #, c-format msgid "stored pre-cherry-pick HEAD file '%s' is corrupt" -msgstr "el fitxer HEAD emmagatzemat abans del recull de cireres '%s' és malmès" +msgstr "el fitxer HEAD emmagatzemat abans de fer «cherry pick» '%s' és malmès" #: sequencer.c:1354 #, c-format msgid "%s: can't cherry-pick a %s" -msgstr "%s: no es pot recollir com a cirera un %s" +msgstr "%s: no es pot fer «cherry pick» a %s" #: sequencer.c:1358 #, c-format @@ -2989,7 +3009,7 @@ msgid "" msgstr "" "Hi ha un problema amb el valor de mode de fitxer core.sharedRepository " "(0%.3o).\n" -"El propietari dels fitxers sempre ha de tenir permissions de lectura i " +"El propietari dels fitxers sempre ha de tenir permisos de lectura i " "escriptura." #: sha1_file.c:473 @@ -3070,7 +3090,7 @@ msgstr "" #: submodule.c:64 submodule.c:98 msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first" msgstr "" -"No es pot canviar un .gitmodules no fusionat, primer resoldreu els " +"No es pot canviar un .gitmodules no fusionat, primer resoleu els " "conflictes de fusió" #: submodule.c:68 submodule.c:102 @@ -3120,7 +3140,7 @@ msgstr "més d'un %s" #: trailer.c:672 #, c-format msgid "empty trailer token in trailer '%.*s'" -msgstr "fitxa de remolc buida en el remolc '%.*s'" +msgstr "testimoni de remolc buit en el remolc '%.*s'" #: trailer.c:695 #, c-format @@ -3219,7 +3239,7 @@ msgstr "mode mal format en entrada d'arbre" #: tree-walk.c:41 msgid "empty filename in tree entry" -msgstr "nom de fitxer buit en entrada de arbre" +msgstr "nom de fitxer buit en una entrada d'arbre" #: tree-walk.c:113 msgid "too-short tree file" @@ -3458,7 +3478,7 @@ msgstr "S'està avortant\n" #: unpack-trees.c:237 msgid "Checking out files" -msgstr "S'està agafant fitxers" +msgstr "S'estan agafant fitxers" #: urlmatch.c:120 msgid "invalid URL scheme name or missing '://' suffix" @@ -3492,7 +3512,7 @@ msgstr "segment de camí '..' no vàlid" #: worktree.c:282 #, c-format msgid "failed to read '%s'" -msgstr "s'ha fallat en llegir '%s'" +msgstr "s'ha produït un error en llegir '%s'" #: wrapper.c:222 wrapper.c:392 #, c-format @@ -3765,7 +3785,7 @@ msgstr "" #: wt-status.c:1236 msgid " (use \"git rebase --skip\" to skip this patch)" -msgstr " (useu \"git rebase --skip\" per a saltar aquest pedaç)" +msgstr " (useu \"git rebase --skip\" per a ometre aquest pedaç)" #: wt-status.c:1238 msgid " (use \"git rebase --abort\" to check out the original branch)" @@ -3797,11 +3817,11 @@ msgstr "" #, c-format msgid "You are currently editing a commit while rebasing branch '%s' on '%s'." msgstr "" -"Actualment esteu editant una comissió mentre rebaseu la branca '%s' en '%s'." +"Actualment esteu editant una comissió mentre es fa «rebase» de la branca '%s' en '%s'." #: wt-status.c:1265 msgid "You are currently editing a commit during a rebase." -msgstr "Actualment esteu editant una comissió durant un rebasament." +msgstr "Actualment esteu editant una comissió durant un «rebase»." #: wt-status.c:1268 msgid " (use \"git commit --amend\" to amend the current commit)" @@ -3817,7 +3837,7 @@ msgstr "" #: wt-status.c:1280 #, c-format msgid "You are currently cherry-picking commit %s." -msgstr "Actualment esteu recollint com a cirera la comissió %s." +msgstr "Actualment esteu fent «cherry pick» a la comissió %s." #: wt-status.c:1285 msgid " (fix conflicts and run \"git cherry-pick --continue\")" @@ -3832,8 +3852,7 @@ msgstr "" #: wt-status.c:1290 msgid " (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)" msgstr "" -" (useu \"git cherry-pick --abort\" per a cancel·lar l'operació de recull de " -"cireres)" +" (useu \"git cherry-pick --abort\" per a cancel·lar l'operació de «cherry pick»)" #: wt-status.c:1299 #, c-format @@ -3873,11 +3892,11 @@ msgstr "En la branca " #: wt-status.c:1530 msgid "interactive rebase in progress; onto " -msgstr "rebasament interactiu en progrés; sobre " +msgstr "«rebase» interactiu en progrés; sobre " #: wt-status.c:1532 msgid "rebase in progress; onto " -msgstr "rebasament en progrés; sobre " +msgstr "«rebase» en progrés; sobre " #: wt-status.c:1537 msgid "HEAD detached at " @@ -3912,7 +3931,7 @@ msgid "" msgstr "" "Ha trigat %.2f segons enumerar els fitxers no seguits.\n" "'status -uno' pot accelerar-ho, però heu d'anar amb compte de no\n" -"oblidar-vos d'afegir fitxers nous per vós mateix (vegeu\n" +"oblidar-vos d'afegir fitxers nous vosaltres mateixos (vegeu\n" "'git help status')." #: wt-status.c:1586 @@ -4014,7 +4033,7 @@ msgstr "no es pot %s: El vostre índex conté canvis sense cometre." #: compat/precompose_utf8.c:57 builtin/clone.c:414 #, c-format msgid "failed to unlink '%s'" -msgstr "s'ha fallat en desenllaçar '%s'" +msgstr "s'ha produït un error en desenllaçar '%s'" #: builtin/add.c:22 msgid "git add [] [--] ..." @@ -4027,7 +4046,7 @@ msgstr "estat de diff inesperat %c" #: builtin/add.c:85 builtin/commit.c:291 msgid "updating files failed" -msgstr "s'ha fallat en actualitzar els fitxers" +msgstr "s'ha produït un error en actualitzar els fitxers" #: builtin/add.c:95 #, c-format @@ -4145,7 +4164,7 @@ msgstr "l'afegiment de fitxers ha fallat" #: builtin/add.c:348 msgid "-A and -u are mutually incompatible" -msgstr "-A i -u són mutualment incompatibles" +msgstr "-A i -u són mútuament incompatibles" #: builtin/add.c:355 msgid "Option --ignore-missing can only be used together with --dry-run" @@ -4190,7 +4209,7 @@ msgstr "Línia d'entrada mal formada: '%s'." #: builtin/am.c:569 #, c-format msgid "Failed to copy notes from '%s' to '%s'" -msgstr "S'ha fallat en copiar les notes de '%s' a '%s'" +msgstr "S'ha produït un error en copiar les notes de '%s' a '%s'" #: builtin/am.c:595 msgid "fseek failed" @@ -4224,11 +4243,11 @@ msgstr "La detecció de format de pedaç ha fallat." #: builtin/am.c:989 builtin/clone.c:379 #, c-format msgid "failed to create directory '%s'" -msgstr "s'ha fallat en crear el directori '%s'" +msgstr "s'ha produït un error en crear el directori '%s'" #: builtin/am.c:993 msgid "Failed to split patches." -msgstr "S'ha fallat en dividir els pedaços." +msgstr "S'ha produït un error en dividir els pedaços." #: builtin/am.c:1125 builtin/commit.c:376 msgid "unable to write index file" @@ -4243,7 +4262,7 @@ msgstr "Quan hàgiu resolt aquest problema, executeu \"%s --continue\"." #, c-format msgid "If you prefer to skip this patch, run \"%s --skip\" instead." msgstr "" -"Si preferiu saltar aquest pedaç, executeu \"%s --skip\" en lloc d'això." +"Si preferiu ometre aquest pedaç, executeu \"%s --skip\" en lloc d'això." #: builtin/am.c:1178 #, c-format @@ -4286,11 +4305,11 @@ msgstr "" #: builtin/am.c:1637 msgid "Falling back to patching base and 3-way merge..." -msgstr "S'està retrocedint a apedaçar la base i fusionar de 3 vies..." +msgstr "S'està retrocedint a apedaçar la base i una fusió de 3 vies..." #: builtin/am.c:1662 msgid "Failed to merge in the changes." -msgstr "S'ha fallat en fusionar els canvis." +msgstr "S'ha produït un error en fusionar els canvis." #: builtin/am.c:1686 builtin/merge.c:628 msgid "git write-tree failed to write a tree" @@ -4303,7 +4322,7 @@ msgstr "s'està aplicant a una història buida" #: builtin/am.c:1706 builtin/commit.c:1769 builtin/merge.c:798 #: builtin/merge.c:823 msgid "failed to write commit object" -msgstr "s'ha fallat en escriure l'objecte de comissió" +msgstr "s'ha produït un error en escriure l'objecte de comissió" #: builtin/am.c:1739 builtin/am.c:1743 #, c-format @@ -4332,7 +4351,7 @@ msgstr "" #: builtin/am.c:1824 #, c-format msgid "Dirty index: cannot apply patches (dirty: %s)" -msgstr "Índex brut: no es pot aplicar pedaços (bruts: %s)" +msgstr "Índex brut: no es poden aplicar pedaços (bruts: %s)" #: builtin/am.c:1861 builtin/am.c:1933 #, c-format @@ -4379,7 +4398,7 @@ msgstr "No s'ha pogut analitzar l'objecte '%s'." #: builtin/am.c:2103 msgid "failed to clean index" -msgstr "s'ha fallat en netejar l'índex" +msgstr "s'ha produït un error en netejar l'índex" #: builtin/am.c:2137 msgid "" @@ -4417,7 +4436,7 @@ msgstr "permet retrocedir a una fusió de 3 vies si és necessari" #: builtin/am.c:2245 builtin/init-db.c:483 builtin/prune-packed.c:57 #: builtin/repack.c:172 msgid "be quiet" -msgstr "calla" +msgstr "silenciós" #: builtin/am.c:2247 msgid "add a Signed-off-by line to the commit message" @@ -4450,7 +4469,7 @@ msgstr "" #: builtin/am.c:2264 msgid "strip everything before a scissors line" -msgstr "despulla tot abans d'una línia de tissores" +msgstr "elimina tot abans d'una línia de tisores" #: builtin/am.c:2266 builtin/am.c:2269 builtin/am.c:2272 builtin/am.c:2275 #: builtin/am.c:2278 builtin/am.c:2281 builtin/am.c:2284 builtin/am.c:2287 @@ -4490,7 +4509,7 @@ msgstr "sinònims de --continue" #: builtin/am.c:2304 msgid "skip the current patch" -msgstr "salta el pedaç actual" +msgstr "omet el pedaç actual" #: builtin/am.c:2307 msgid "restore the original branch and abort the patching operation." @@ -4527,7 +4546,7 @@ msgstr "" #: builtin/am.c:2341 msgid "failed to read the index" -msgstr "s'ha fallat en llegir l'índex" +msgstr "S'ha produït un error en llegir l'índex" #: builtin/am.c:2356 #, c-format @@ -4586,7 +4605,7 @@ msgstr "git archive: error de protocol" #: builtin/archive.c:68 msgid "git archive: expected a flush" -msgstr "git archive: rentada esperada" +msgstr "git archive: s'esperava una neteja" #: builtin/bisect--helper.c:7 msgid "git bisect--helper --next-all [--no-checkout]" @@ -4610,11 +4629,11 @@ msgstr "es documenten les en git-rev-list(1)" #: builtin/blame.c:1781 msgid "Blaming lines" -msgstr "S'estan culpant les línies" +msgstr "S'està fent un «blame»" #: builtin/blame.c:2577 msgid "Show blame entries as we find them, incrementally" -msgstr "Mostra les entrades de culpa mentre les trobem, incrementalment" +msgstr "Mostra les entrades «blame» mentre les trobem, incrementalment" #: builtin/blame.c:2578 msgid "Show blank SHA-1 for boundary commits (Default: off)" @@ -4637,7 +4656,7 @@ msgstr "Força l'informe de progrés" #: builtin/blame.c:2582 msgid "Show output score for blame entries" -msgstr "Mostra la puntuació de sortida de les entrades de culpa" +msgstr "Mostra la puntuació de sortida de les entrades «blame»" #: builtin/blame.c:2583 msgid "Show original filename (Default: auto)" @@ -4742,7 +4761,7 @@ msgstr "fa 4 anys i 11 mesos" #: builtin/blame.c:2780 msgid "--contents and --reverse do not blend well." -msgstr "--contents i --reverse no es jutgen bé." +msgstr "--contents i --reverse no funcionen bé juntes." #: builtin/blame.c:2800 msgid "cannot use --contents with final commit object name" @@ -4954,7 +4973,7 @@ msgstr "(cap branca)" #: builtin/branch.c:544 #, c-format msgid "Branch %s is being rebased at %s" -msgstr "La branca %s s'està rebasant a %s" +msgstr "S'està fent «rebase» en la branca %s a %s" #: builtin/branch.c:548 #, c-format @@ -4977,7 +4996,7 @@ msgstr "El canvi de nom de branca ha fallat" #: builtin/branch.c:594 #, c-format msgid "Renamed a misnamed branch '%s' away" -msgstr "S'ha canviat el nom de la branca malanomenada '%s'" +msgstr "S'ha canviat el nom de la branca mal anomenada '%s'" #: builtin/branch.c:597 #, c-format @@ -4999,7 +5018,7 @@ msgid "" msgstr "" "Si us plau, editeu la descripció de la branca\n" " %s\n" -"Es despullaran les línies que comencin amb '%c'.\n" +"S'eliminaran les línies que comencin amb '%c'.\n" #: builtin/branch.c:651 msgid "Generic options" @@ -5117,7 +5136,7 @@ msgstr "imprimeix només les branques de l'objecte" #: builtin/branch.c:705 msgid "Failed to resolve HEAD as a valid ref." -msgstr "S'ha fallat en resoldre HEAD com a referència vàlida." +msgstr "S'ha produït un error en resoldre HEAD com a referència vàlida." #: builtin/branch.c:709 builtin/clone.c:706 msgid "HEAD not found below refs/heads!" @@ -5203,7 +5222,7 @@ msgid "" "The --set-upstream flag is deprecated and will be removed. Consider using --" "track or --set-upstream-to\n" msgstr "" -"La bandera --set-upstream està desaprovada i s'eliminarà. Considereu usar --" +"La bandera --set-upstream està en desús i s'eliminarà. Considereu usar --" "track o --set-upstream-to\n" #: builtin/branch.c:866 @@ -5281,7 +5300,7 @@ msgstr "blob" #: builtin/cat-file.c:562 msgid "use a specific path for --textconv/--filters" -msgstr "usa un camí especìfic per a --textconv/--filters" +msgstr "usa un camí específic per a --textconv/--filters" #: builtin/cat-file.c:564 msgid "allow -s and -t to work with broken/corrupt objects" @@ -5335,7 +5354,7 @@ msgstr "acaba els registres d'entrada i de sortida amb un caràcter NUL" #: builtin/check-ignore.c:18 builtin/checkout.c:1137 builtin/gc.c:325 msgid "suppress progress reporting" -msgstr "omet el reportatge de progrés" +msgstr "omet els informes de progrés" #: builtin/check-ignore.c:26 msgid "show non-matching input paths" @@ -5404,7 +5423,7 @@ msgstr "força la sobreescriptura de fitxers existents" #: builtin/checkout-index.c:163 msgid "no warning for existing files and files not in index" -msgstr "cap advertència per a fitxers existents i fitxers no en l'índex" +msgstr "cap advertència per a fitxers existents i fitxers que no siguin a l'índex" #: builtin/checkout-index.c:165 msgid "don't checkout new files" @@ -5448,12 +5467,12 @@ msgstr "git checkout [] [] -- ..." #: builtin/checkout.c:134 builtin/checkout.c:167 #, c-format msgid "path '%s' does not have our version" -msgstr "el camí '%s' no té la versió nostra" +msgstr "el camí '%s' no té la nostra versió" #: builtin/checkout.c:136 builtin/checkout.c:169 #, c-format msgid "path '%s' does not have their version" -msgstr "el camí '%s' no té la versió seva" +msgstr "el camí '%s' no té la seva versió" #: builtin/checkout.c:152 #, c-format @@ -5490,7 +5509,7 @@ msgstr "'%s' no es pot usar amb %s" #, c-format msgid "Cannot update paths and switch to branch '%s' at the same time." msgstr "" -"No es pot actualitzar els camins i canviar a la branca '%s' a la vegada." +"No es poden actualitzar els camins i canviar a la branca '%s' a la vegada." #: builtin/checkout.c:339 builtin/checkout.c:346 #, c-format @@ -5527,17 +5546,17 @@ msgstr "Ja en '%s'\n" #: builtin/checkout.c:678 #, c-format msgid "Switched to and reset branch '%s'\n" -msgstr "S'ha agafat i restablert la branca '%s'\n" +msgstr "S'ha canviat i restablert a la branca '%s'\n" #: builtin/checkout.c:680 builtin/checkout.c:1069 #, c-format msgid "Switched to a new branch '%s'\n" -msgstr "S'ha agafat la branca nova '%s'\n" +msgstr "S'ha canviat a la branca nova '%s'\n" #: builtin/checkout.c:682 #, c-format msgid "Switched to branch '%s'\n" -msgstr "S'ha agafat la branca '%s'\n" +msgstr "S'ha canviat a la branca '%s'\n" #: builtin/checkout.c:733 #, c-format @@ -5604,7 +5623,7 @@ msgstr "La posició de HEAD anterior era" #: builtin/checkout.c:825 builtin/checkout.c:1064 msgid "You are on a branch yet to be born" -msgstr "Sou en una branca que encara ha de nàixer" +msgstr "Sou en una branca que encara ha de néixer" #: builtin/checkout.c:970 #, c-format @@ -5778,17 +5797,17 @@ msgstr "Eliminaria %s\n" #: builtin/clean.c:31 #, c-format msgid "Skipping repository %s\n" -msgstr "S'està saltant el dipòsit %s\n" +msgstr "S'està ometent el dipòsit %s\n" #: builtin/clean.c:32 #, c-format msgid "Would skip repository %s\n" -msgstr "Saltaria el dipòsit %s\n" +msgstr "Ometria el dipòsit %s\n" #: builtin/clean.c:33 #, c-format msgid "failed to remove %s" -msgstr "s'ha fallat en eliminar %s" +msgstr "s'ha produït un error en eliminar %s" #: builtin/clean.c:291 msgid "" @@ -5797,7 +5816,7 @@ msgid "" "foo - select item based on unique prefix\n" " - (empty) select nothing" msgstr "" -"Ajuda d'avís:\n" +"Ajuda:\n" "1 - selecciona un ítem numerat\n" "foo - selecciona un ítem basat en un prefix únic\n" " - (buit) no seleccionis res" @@ -5813,7 +5832,7 @@ msgid "" "* - choose all items\n" " - (empty) finish selecting" msgstr "" -"Ajuda d'avís:\n" +"Ajuda:\n" "1 - selecciona un sol ítem\n" "3-5 - selecciona un rang d'ítems\n" "2-3,6-9 - selecciona múltiples rangs\n" @@ -5835,7 +5854,7 @@ msgstr "Introduïu els patrons a ignorar>> " #: builtin/clean.c:690 #, c-format msgid "WARNING: Cannot find items matched by: %s" -msgstr "ADVERTÈNCIA: No es pot trobar ítems que coincideixin amb: %s" +msgstr "ADVERTÈNCIA: No es poden trobar ítems que coincideixin amb: %s" #: builtin/clean.c:711 msgid "Select items to delete" @@ -5929,7 +5948,7 @@ msgid "" "clean.requireForce set to true and neither -i, -n, nor -f given; refusing to " "clean" msgstr "" -"clean.requireForce està establerta a veritat i ni -i, -n ni -f s'ha donat; " +"clean.requireForce està establerta a veritat i ni -i, -n ni -f s'han indicat; " "refusant netejar" #: builtin/clean.c:904 @@ -5937,7 +5956,7 @@ msgid "" "clean.requireForce defaults to true and neither -i, -n, nor -f given; " "refusing to clean" msgstr "" -"clean.requireForce és per defecte veritat i ni -i, -n ni -f s'ha donat; " +"clean.requireForce és per defecte veritat i ni -i, -n ni -f s'han indicat; " "refusant netejar" #: builtin/clone.c:37 @@ -5954,7 +5973,7 @@ msgstr "crea un dipòsit nu" #: builtin/clone.c:70 msgid "create a mirror repository (implies bare)" -msgstr "crea un dipòsit reflectit (implica bare)" +msgstr "crea un dipòsit mirall (implica bare)" #: builtin/clone.c:72 msgid "to clone from a local repository" @@ -5982,7 +6001,7 @@ msgstr "directori-de-plantilla" #: builtin/clone.c:84 builtin/init-db.c:476 msgid "directory from which templates will be used" -msgstr "directori del qual les plantilles s'usaran" +msgstr "directori des del qual s'usaran les plantilles" #: builtin/clone.c:86 builtin/clone.c:88 builtin/submodule--helper.c:602 #: builtin/submodule--helper.c:954 @@ -6060,11 +6079,11 @@ msgstr "estableix la configuració dins del dipòsit nou" #: builtin/clone.c:111 builtin/fetch.c:140 builtin/push.c:547 msgid "use IPv4 addresses only" -msgstr "usa només les adreces IPv4" +msgstr "usa només adreces IPv4" #: builtin/clone.c:113 builtin/fetch.c:142 builtin/push.c:549 msgid "use IPv6 addresses only" -msgstr "usa només les adreces IPv6" +msgstr "usa només adreces IPv6" #: builtin/clone.c:250 msgid "" @@ -6082,7 +6101,7 @@ msgstr "info: No s'ha pogut afegir un alternatiu per a '%s': %s\n" #: builtin/clone.c:375 #, c-format msgid "failed to open '%s'" -msgstr "s'ha fallat en obrir '%s'" +msgstr "s'ha produït un error en obrir '%s'" #: builtin/clone.c:383 #, c-format @@ -6092,17 +6111,17 @@ msgstr "%s existeix i no és directori" #: builtin/clone.c:397 #, c-format msgid "failed to stat %s\n" -msgstr "s'ha fallat en fer stat a '%s'\n" +msgstr "s'ha produït un error en fer stat a '%s'\n" #: builtin/clone.c:419 #, c-format msgid "failed to create link '%s'" -msgstr "s'ha fallat en crear l'enllaç '%s'" +msgstr "s'ha produït un error en crear l'enllaç '%s'" #: builtin/clone.c:423 #, c-format msgid "failed to copy file to '%s'" -msgstr "s'ha fallat en copiar el fitxer a '%s'" +msgstr "s'ha produït un error en copiar el fitxer a '%s'" #: builtin/clone.c:448 #, c-format @@ -6197,7 +6216,7 @@ msgstr "l'arbre de treball '%s' ja existeix." #: builtin/worktree.c:222 builtin/worktree.c:249 #, c-format msgid "could not create leading directories of '%s'" -msgstr "no s'ha pogut crear els directoris inicials de '%s'" +msgstr "no s'han pogut crear els directoris inicials de '%s'" #: builtin/clone.c:943 #, c-format @@ -6219,7 +6238,7 @@ msgid "" "clone --recursive is not compatible with both --reference and --reference-if-" "able" msgstr "" -"clone --recursive no és compatible amb ambdòs --reference i --reference-if-" +"clone --recursive no és compatible amb ambdós --reference i --reference-if-" "able" #: builtin/clone.c:1019 @@ -6369,7 +6388,7 @@ msgid "" " git commit --allow-empty\n" "\n" msgstr "" -"El recull de cireres previ ja està buit, possiblement a causa de resolució " +"El «cherry pick» previ ja està buit, possiblement a causa de resolució " "de conflicte.\n" "Si el voleu cometre de totes maneres, useu:\n" "\n" @@ -6393,12 +6412,12 @@ msgstr "" "\n" " git reset\n" "\n" -"Llavors \"git cherry-pick --continue\" reprendrà recollint\n" -"com a cireres les comissions restants.\n" +"Llavors \"git cherry-pick --continue\" reprendrà\n" +"com a «cherry pick» les comissions restants.\n" #: builtin/commit.c:318 msgid "failed to unpack HEAD tree object" -msgstr "s'ha fallat en desempaquetar l'objecte d'arbre HEAD" +msgstr "s'ha produït un error en desempaquetar l'objecte d'arbre HEAD" #: builtin/commit.c:359 msgid "unable to create temporary index" @@ -6414,7 +6433,7 @@ msgstr "no s'ha pogut actualitzar l'índex temporal" #: builtin/commit.c:380 msgid "Failed to update main cache tree" -msgstr "S'ha fallat en actualitzar l'arbre principal de memòria cau" +msgstr "s'ha produït un error en actualitzar l'arbre principal de memòria cau" #: builtin/commit.c:404 builtin/commit.c:427 builtin/commit.c:476 msgid "unable to write new_index file" @@ -6426,7 +6445,7 @@ msgstr "no es pot fer una comissió parcial durant una fusió." #: builtin/commit.c:460 msgid "cannot do a partial commit during a cherry-pick." -msgstr "no es pot fer una comissió parcial durant un recull de cireres." +msgstr "no es pot fer una comissió parcial durant un «cherry pick»." #: builtin/commit.c:469 msgid "cannot read the index" @@ -6519,7 +6538,7 @@ msgid "" "and try again.\n" msgstr "" "\n" -"Sembla que podeu estar cometent un recull de cireres.\n" +"Sembla que podeu estar cometent un «cherry pick».\n" "Si això no és correcte, si us plau, elimineu el fitxer\n" "\t%s\n" "i intenteu-ho de nou.\n" @@ -6542,8 +6561,8 @@ msgid "" "An empty message aborts the commit.\n" msgstr "" "Si us plau, introduïu el missatge de comissió dels vostres canvis.\n" -"Es retindran les línies començants amb '%c'; podeu eliminar-les per vós\n" -"mateix si voleu. Un missatge buit avorta la comissió.\n" +"Es retindran les línies començants amb '%c'; podeu eliminar-les vosaltres\n" +"mateixos si voleu. Un missatge buit avorta la comissió.\n" #: builtin/commit.c:859 #, c-format @@ -6571,7 +6590,7 @@ msgstr "Error en construir arbres" #: builtin/commit.c:969 builtin/tag.c:266 #, c-format msgid "Please supply the message using either -m or -F option.\n" -msgstr "Si us plau, proveïu el missatge per usar o l'opció -m o l'opció -F.\n" +msgstr "Si us plau, proveïu el missatge usant l'opció -m o l'opció -F.\n" #: builtin/commit.c:1071 #, c-format @@ -6603,7 +6622,7 @@ msgstr "Esteu enmig d'una fusió -- no es pot esmenar." #: builtin/commit.c:1168 msgid "You are in the middle of a cherry-pick -- cannot amend." -msgstr "Esteu enmig d'un recull de cireres -- no es pot esmenar." +msgstr "Esteu enmig d'un «cherry pick» -- no es pot esmenar." #: builtin/commit.c:1171 msgid "Options --squash and --fixup cannot be used together" @@ -6636,8 +6655,8 @@ msgstr "Intel·ligent... s'està esmenant l'últim amb índex brut." #: builtin/commit.c:1214 msgid "Explicit paths specified without -i or -o; assuming --only paths..." msgstr "" -"S'han especificat camins explícits sense -i o -o; s'està presumint camins --" -"only..." +"S'han especificat camins explícits sense -i o -o; s'està presumint --" +"only camins..." #: builtin/commit.c:1226 builtin/tag.c:474 #, c-format @@ -6808,7 +6827,7 @@ msgstr "per defecte" #: builtin/commit.c:1603 builtin/tag.c:354 msgid "how to strip spaces and #comments from message" -msgstr "com despullar els espais i #comentaris del missatge" +msgstr "com suprimir els espais i #comentaris del missatge" #: builtin/commit.c:1604 msgid "include status in commit message template" @@ -6857,7 +6876,7 @@ msgstr "esmena la comissió anterior" #: builtin/commit.c:1628 msgid "bypass post-rewrite hook" -msgstr "evita el ganxo de postreescriure" +msgstr "evita el ganxo de post escriptura" #: builtin/commit.c:1633 msgid "ok to record an empty change" @@ -7038,7 +7057,7 @@ msgstr "respecta les directives d'inclusió en cercar" #: builtin/config.c:85 msgid "show origin of config (file, standard input, blob, command line)" msgstr "" -"mostra l'origen de la configuració (fitxer, entrada estàndar, blob, línia " +"mostra l'origen de la configuració (fitxer, entrada estàndard, blob, línia " "d'ordres)" #: builtin/config.c:327 @@ -7072,7 +7091,7 @@ msgid "" " Use a regexp, --add or --replace-all to change %s." msgstr "" "no es pot sobreescriure múltiples valors amb un sol valor\n" -" Useu una expresió regular, --add o --replace-all per a canviar %s." +" Useu una expressió regular, --add o --replace-all per a canviar %s." #: builtin/count-objects.c:86 msgid "git count-objects [-v] [-H | --human-readable]" @@ -7172,11 +7191,11 @@ msgstr "estratègia de cerca de depuració en stderr" #: builtin/describe.c:398 msgid "use any ref" -msgstr "usa qualsevulla referència" +msgstr "usa qualsevol referència" #: builtin/describe.c:399 msgid "use any tag, even unannotated" -msgstr "usa qualsevulla etiqueta, fins i tot aquelles sense anotar" +msgstr "usa qualsevol etiqueta, fins i tot aquelles sense anotar" #: builtin/describe.c:400 msgid "always use long format" @@ -7289,7 +7308,7 @@ msgstr "Usa la característica done per a acabar el corrent" #: builtin/fast-export.c:997 msgid "Skip output of blob data" -msgstr "Salta l'emissió de dades de blob" +msgstr "Omet l'emissió de dades de blob" #: builtin/fast-export.c:998 msgid "refspec" @@ -7486,7 +7505,7 @@ msgid "" msgstr "" "algunes referències locals no s'han pogut actualitzar;\n" " intenteu executar 'git remote prune %s' per a eliminar\n" -" qualsevulla branca antiga o conflictiva" +" qualsevol branca antiga o conflictiva" #: builtin/fetch.c:922 #, c-format @@ -7541,7 +7560,7 @@ msgid "" "No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched." msgstr "" -"Cap dipòsit remot especificat. Si us plau, especifiqueu o un URL o\n" +"Cap dipòsit remot especificat. Si us plau, especifiqueu un URL o\n" "un nom remot del qual es deuen obtenir les revisions noves." #: builtin/fetch.c:1280 @@ -7593,7 +7612,7 @@ msgstr "emplena el registre amb entrades del registre curt com a màxim" #: builtin/fmt-merge-msg.c:666 msgid "alias for --log (deprecated)" -msgstr "àlies per --log (desaprovat)" +msgstr "àlies per --log (en desús)" #: builtin/fmt-merge-msg.c:669 msgid "text" @@ -7748,7 +7767,7 @@ msgid "" "\n" "%s" msgstr "" -"L'última execució de gc ha informat d'ho següent. Si us plau, corregiu\n" +"L'última execució de gc ha informat el següent. Si us plau, corregiu\n" "la causa primordial i elimineu %s.\n" "No es realitzarà la neteja automàtica fins que s'elimini el fitxer.\n" "\n" @@ -7808,7 +7827,7 @@ msgstr "git grep [] [-e] [...] [[--] ...]" #: builtin/grep.c:219 #, c-format msgid "grep: failed to create thread: %s" -msgstr "grep: s'ha fallat en crear fil: %s" +msgstr "grep: s'ha produït un error en crear fil: %s" #: builtin/grep.c:277 #, c-format @@ -7954,7 +7973,7 @@ msgstr "mostra línies de context després d'una coincidència" #: builtin/grep.c:715 msgid "use worker threads" -msgstr "usa fils obrers" +msgstr "usa fils de treball" #: builtin/grep.c:716 msgid "shortcut for -C NUM" @@ -8071,7 +8090,7 @@ msgstr "emmagatzema el fitxer tal com és sense filtres" msgid "" "just hash any random garbage to create corrupt objects for debugging Git" msgstr "" -"només suma qualsevulla brossa aleatòria per a crear objectes malmesos per a " +"només suma qualsevol brossa aleatòria per a crear objectes malmesos per a " "depurar al Git" #: builtin/hash-object.c:101 @@ -8084,7 +8103,7 @@ msgstr "imprimeix totes les ordres disponibles" #: builtin/help.c:43 msgid "exclude guides" -msgstr "exclou guíes" +msgstr "exclou guies" #: builtin/help.c:44 msgid "print list of useful guides" @@ -8113,11 +8132,11 @@ msgstr "format d'ajuda no reconegut '%s'" #: builtin/help.c:93 msgid "Failed to start emacsclient." -msgstr "S'ha fallat en iniciar emacsclient." +msgstr "S'ha produït un error'ha produït un error en iniciar emacsclient." #: builtin/help.c:106 msgid "Failed to parse emacsclient version." -msgstr "S'ha fallat en analitzar la versió d'emacsclient." +msgstr "S'ha produït un error en analitzar la versió d'emacsclient." #: builtin/help.c:114 #, c-format @@ -8127,7 +8146,7 @@ msgstr "la versió d'emacsclient '%d' és massa vella (< 22)." #: builtin/help.c:132 builtin/help.c:153 builtin/help.c:162 builtin/help.c:170 #, c-format msgid "failed to exec '%s'" -msgstr "s'ha fallat en executar '%s'" +msgstr "s'ha produït un error en executar '%s'" #: builtin/help.c:207 #, c-format @@ -8186,7 +8205,7 @@ msgstr "L'especificació de revisions i rangs per al Git" #: builtin/help.c:409 msgid "A tutorial introduction to Git (for version 1.5.1 or newer)" -msgstr "Una introducció tutorial al Git (per a la versió 1.5.1 o més nou)" +msgstr "Una introducció tutorial al Git (per a la versió 1.5.1 o més nova)" #: builtin/help.c:410 msgid "An overview of recommended workflows with Git" @@ -8349,7 +8368,7 @@ msgstr "No tots els objectes fills de %s són abastables" #: builtin/index-pack.c:922 builtin/index-pack.c:953 msgid "failed to apply delta" -msgstr "s'ha fallat en aplicar la delta" +msgstr "s'ha produït un error en aplicar la delta" #: builtin/index-pack.c:1123 msgid "Receiving objects" @@ -8618,7 +8637,7 @@ msgid "" "[(=|:)])...] [...]" msgstr "" "git interpret-trailers [--in-place] [--trim-empty] [(--trailer " -"[(=|:)])...] [...]" +"[(=|:)])...] [...]" #: builtin/interpret-trailers.c:26 msgid "edit files in place" @@ -8739,7 +8758,7 @@ msgstr "Comissió desconeguda %s" #: builtin/log.c:1258 builtin/notes.c:884 builtin/tag.c:455 #, c-format msgid "Failed to resolve '%s' as a valid ref." -msgstr "S'ha fallat en resoldre '%s' com a referència vàlida." +msgstr "S'ha produït un error en resoldre '%s' com a referència vàlida." #: builtin/log.c:1263 msgid "Could not find exact merge base." @@ -8751,14 +8770,14 @@ msgid "" "please use git branch --set-upstream-to to track a remote branch.\n" "Or you could specify base commit by --base= manually." msgstr "" -"S'ha fallat en obtenir la font. Si voleu registrar la comissió base\n" +"S'ha produït un error en obtenir la font. Si voleu registrar la comissió base\n" "automàticament, si us plau, useu git branch --set-upstream-to per a\n" "seguir una branca remot. O podeu especificar la comissió base manualment\n" "amb --base=." #: builtin/log.c:1287 msgid "Failed to find exact merge base" -msgstr "S'ha fallat en trobar la base exacta de fusió." +msgstr "S'ha produït un error en trobar la base exacta de fusió." #: builtin/log.c:1298 msgid "base commit should be the ancestor of revision list" @@ -8958,7 +8977,7 @@ msgstr "no s'ha pogut llegir el fitxer de signatura '%s'" #: builtin/log.c:1777 msgid "Failed to create output files" -msgstr "S'ha fallat en crear els fitxers de sortida" +msgstr "S'ha produït un error en crear els fitxers de sortida" #: builtin/log.c:1826 msgid "git cherry [-v] [ [ []]]" @@ -9035,7 +9054,7 @@ msgstr "mostra la informació de resolució de desfet" #: builtin/ls-files.c:537 msgid "skip files matching pattern" -msgstr "salta els fitxers coincidents amb el patró" +msgstr "omet els fitxers coincidents amb el patró" #: builtin/ls-files.c:540 msgid "exclude patterns are read from " @@ -9059,7 +9078,7 @@ msgstr "recursa als submòduls" #: builtin/ls-files.c:553 msgid "if any is not in the index, treat this as an error" -msgstr "si qualsevol no és en l'índex, tracta això com a error" +msgstr "si qualsevol no és en l'índex, tracta-ho com a error" #: builtin/ls-files.c:554 msgid "tree-ish" @@ -9416,13 +9435,13 @@ msgid "" "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge." msgstr "" -"No heu conclòs el vostre recull de cireres (CHERRY_PICK_HEAD existeix).\n" +"No heu conclòs el vostre «cherry pick» (CHERRY_PICK_HEAD existeix).\n" "Si us plau, cometeu els vostres canvis abans de fusionar." #: builtin/merge.c:1188 msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)." msgstr "" -"No heu conclòs el vostre recull de cireres (CHERRY_PICK_HEAD existeix)." +"No heu conclòs el vostre «cherry pick» (CHERRY_PICK_HEAD existeix)." #: builtin/merge.c:1197 msgid "You cannot combine --squash with --no-ff." @@ -9593,11 +9612,11 @@ msgstr "usa una fusió basada en diff3" #: builtin/merge-file.c:35 msgid "for conflicts, use our version" -msgstr "en conflictes, usa la versió nostra" +msgstr "en conflictes, usa la nostra versió" #: builtin/merge-file.c:37 msgid "for conflicts, use their version" -msgstr "en conflictes, usa la versió seva" +msgstr "en conflictes, usa la seva versió" #: builtin/merge-file.c:39 msgid "for conflicts, use a union version" @@ -9911,7 +9930,7 @@ msgstr "git notes get-ref" #: builtin/notes.c:94 msgid "Write/edit the notes for the following object:" -msgstr "Escriviu/editeu les notes de l'objecte següent:" +msgstr "Escriviu/editeu les notes per l'objecte següent:" #: builtin/notes.c:147 #, c-format @@ -9925,7 +9944,7 @@ msgstr "no s'ha pogut llegir la sortida de 'show'" #: builtin/notes.c:159 #, c-format msgid "failed to finish 'show' for object '%s'" -msgstr "s'ha fallat en finalitzar 'show' per a l'objecte '%s'" +msgstr "S'ha produït un error en finalitzar 'show' per a l'objecte '%s'" #: builtin/notes.c:194 msgid "please supply the note contents using either -m or -F option" @@ -9957,12 +9976,12 @@ msgstr "no s'ha pogut obrir o llegir '%s'" #: builtin/notes.c:518 builtin/notes.c:596 builtin/notes.c:659 #, c-format msgid "failed to resolve '%s' as a valid ref." -msgstr "s'ha fallat en resoldre '%s' com a referència vàlida." +msgstr "s'ha produït un error en resoldre '%s' com a referència vàlida." #: builtin/notes.c:257 #, c-format msgid "failed to read object '%s'." -msgstr "s'ha fallat en llegir l'objecte '%s'." +msgstr "s'ha produït un error en llegir l'objecte '%s'." #: builtin/notes.c:261 #, c-format @@ -9977,7 +9996,7 @@ msgstr "línia d'entrada mal formada: '%s'." #: builtin/notes.c:316 #, c-format msgid "failed to copy notes from '%s' to '%s'" -msgstr "s'ha fallat en copiar les notes de '%s' a '%s'" +msgstr "s'ha produït un error en copiar les notes de '%s' a '%s'" #. TRANSLATORS: the first %s will be replaced by a #. git notes command: 'add', 'merge', 'remove', etc. @@ -10027,7 +10046,7 @@ msgid "" "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" msgstr "" -"No es pot afegir les notes. S'han trobat notes existents de l'objecte %s. " +"No es poden afegir les notes. S'han trobat notes existents de l'objecte %s. " "Useu '-f' per a sobreescriure les notes existents." #: builtin/notes.c:452 builtin/notes.c:531 @@ -10059,7 +10078,7 @@ msgid "" "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" msgstr "" -"No es pot copiar les notes. S'han trobat notes existents de l'objecte %s. " +"No es poden copiar les notes. S'han trobat notes existents de l'objecte %s. " "Useu '-f' per a sobreescriure les notes existents." #: builtin/notes.c:537 @@ -10073,24 +10092,24 @@ 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 "" -"S'han desaprovat les opcions -m/-F/-c/-C en favor de la subordre 'edit'.\n" +"Es desaconsellen les opcions -m/-F/-c/-C en favor de la subordre 'edit'.\n" "Si us plau, useu 'git notes add -f -m/-F/-c/-C' en lloc d'això.\n" #: builtin/notes.c:685 msgid "failed to delete ref NOTES_MERGE_PARTIAL" -msgstr "s'ha fallat en suprimir la referència NOTES_MERGE_PARTIAL" +msgstr "s'ha produït un error en suprimir la referència NOTES_MERGE_PARTIAL" #: builtin/notes.c:687 msgid "failed to delete ref NOTES_MERGE_REF" -msgstr "s'ha fallat en suprimir la referència NOTES_MERGE_REF" +msgstr "s'ha produït un error en suprimir la referència NOTES_MERGE_REF" #: builtin/notes.c:689 msgid "failed to remove 'git notes merge' worktree" -msgstr "s'ha fallat en eliminar l'arbre de treball de 'git notes merge'" +msgstr "s'ha produït un error en eliminar l'arbre de treball de 'git notes merge'" #: builtin/notes.c:709 msgid "failed to read ref NOTES_MERGE_PARTIAL" -msgstr "s'ha fallat en llegir la referència NOTES_MERGE_PARTIAL" +msgstr "s'ha produït un error en llegir la referència NOTES_MERGE_PARTIAL" #: builtin/notes.c:711 msgid "could not find commit from NOTES_MERGE_PARTIAL." @@ -10102,11 +10121,11 @@ msgstr "no s'ha pogut analitzar la comissió de NOTES_MERGE_PARTIAL." #: builtin/notes.c:726 msgid "failed to resolve NOTES_MERGE_REF" -msgstr "s'ha fallat en resoldre NOTES_MERGE_REF" +msgstr "s'ha produït un error en resoldre NOTES_MERGE_REF" #: builtin/notes.c:729 msgid "failed to finalize notes merge" -msgstr "s'ha fallat en finalitzar la fusió de notes" +msgstr "s'ha produït un error en finalitzar la fusió de notes" #: builtin/notes.c:755 #, c-format @@ -10167,7 +10186,7 @@ msgstr "una fusió de notes a %s ja està en curs a %s" #, c-format msgid "failed to store link to current notes ref (%s)" msgstr "" -"s'ha fallat en emmagatzemar l'enllaç a la referència de notes actual (%s)" +"s'ha produït un error en emmagatzemar l'enllaç a la referència de notes actual (%s)" #: builtin/notes.c:865 #, c-format @@ -10471,7 +10490,7 @@ msgstr "permet l'avanç ràpid" #: builtin/pull.c:157 msgid "automatically stash/stash pop before and after rebase" -msgstr "automàticament emmagatzema/desempila abans i després de rebasament" +msgstr "automàticament emmagatzema/desempila abans i després de fer «rebase»" #: builtin/pull.c:173 msgid "Options related to fetching" @@ -10525,7 +10544,7 @@ msgstr "Actualment no sou en cap branca." #: builtin/pull.c:410 builtin/pull.c:425 git-parse-remote.sh:79 msgid "Please specify which branch you want to rebase against." -msgstr "Si us plau, especifiqueu sobre què branca voleu rebasar." +msgstr "Si us plau, especifiqueu sobre què branca voleu fer «rebase»." #: builtin/pull.c:412 builtin/pull.c:427 git-parse-remote.sh:82 msgid "Please specify which branch you want to merge with." @@ -10567,7 +10586,7 @@ msgstr "" #: builtin/pull.c:754 msgid "ignoring --verify-signatures for rebase" -msgstr "s'està ignorant --verify-signatures per a rebasar" +msgstr "s'està ignorant --verify-signatures en fer «rebase»" #: builtin/pull.c:801 msgid "--[no-]autostash option is only valid with --rebase." @@ -10580,7 +10599,7 @@ msgstr "" #: builtin/pull.c:812 msgid "pull with rebase" -msgstr "baixar amb rebasament" +msgstr "baixar amb fent «rebase»" #: builtin/pull.c:813 msgid "please commit or stash them." @@ -10617,11 +10636,11 @@ msgstr "" #: builtin/pull.c:858 msgid "Cannot merge multiple branches into empty head." -msgstr "No es pot fusionar múltiples branques a un cap buit." +msgstr "No es poden fusionar múltiples branques a un cap buit." #: builtin/pull.c:862 msgid "Cannot rebase onto multiple branches." -msgstr "No es pot rebasar sobre múltiples branques." +msgstr "No es pot fer «rebase» sobre múltiples branques." #: builtin/push.c:16 msgid "git push [] [ [...]]" @@ -10641,7 +10660,7 @@ msgid "" "To choose either option permanently, see push.default in 'git help config'." msgstr "" "\n" -"Per a triar qualsevulla opció permanentment, vegeu push.default a 'git help " +"Per a triar qualsevol opció permanentment, vegeu push.default a 'git help " "config'." #: builtin/push.c:146 @@ -10785,7 +10804,7 @@ msgstr "S'està pujant a %s\n" #: builtin/push.c:335 #, c-format msgid "failed to push some refs to '%s'" -msgstr "s'ha fallat en pujar algunes referències a '%s'" +msgstr "s'ha produït un error en pujar algunes referències a '%s'" #: builtin/push.c:366 #, c-format @@ -10943,7 +10962,7 @@ msgstr "només buida l'índex" #: builtin/read-tree.c:115 msgid "Merging" -msgstr "Fusión" +msgstr "S'està fusionant" #: builtin/read-tree.c:117 msgid "perform a merge in addition to a read" @@ -10975,7 +10994,7 @@ msgstr "actualitza l'arbre de treball amb el resultat de fusió" #: builtin/read-tree.c:130 msgid "gitignore" -msgstr "ignoral de git" +msgstr "gitignore" #: builtin/read-tree.c:131 msgid "allow explicitly ignored files to be overwritten" @@ -10991,7 +11010,7 @@ msgstr "no actualitzis l'índex ni l'arbre de treball" #: builtin/read-tree.c:137 msgid "skip applying sparse checkout filter" -msgstr "salta l'aplicació del filtre d'agafament parcial" +msgstr "omet l'aplicació del filtre d'agafament parcial" #: builtin/read-tree.c:139 msgid "debug unpack-trees" @@ -11017,16 +11036,16 @@ msgid "" "To squelch this message and still keep the default behaviour, set\n" "'receive.denyCurrentBranch' configuration variable to 'refuse'." msgstr "" -"Per defecte, es denega actualizar la branca actual en un dipòsit no\n" -"nu, perquè feria l'índex i l'arbre de treball inconsistents amb el\n" -"que hàgiu pujat, i requerria 'git reset --hard' per a fer que\n" +"Per defecte, es denega actualitzar la branca actual en un dipòsit no\n" +"nu, perquè faria l'índex i l'arbre de treball inconsistents amb el\n" +"que hàgiu pujat, i requeriria 'git reset --hard' per a fer que\n" "l'arbre de treball coincideixi amb HEAD.\n" "\n" "Podeu establir la variable de configuració\n" "'receive.denyCurrentBranch' a 'ignore' o 'warn' en el dipòsit remot\n" "per a permetre pujar a la seva branca actual; no obstant, no es\n" "recomana això a menys que hàgiu decidit actualitzar el seu arbre en\n" -"alguna altra manera per a coincidar amb el que hàgiu pujat.\n" +"alguna altra manera per a coincidir amb el que hàgiu pujat.\n" "\n" "Per a silenciar aquest missatge i encara retenir el comportament\n" "predeterminat, establiu la variable de configuració\n" @@ -11049,14 +11068,14 @@ msgstr "" "\n" "Podeu establir la variable de configuració\n" "'receive.denyDeleteCurrent' a 'warn' o 'ignore' en el dipòsit remot\n" -"per a permetre suprimir la branca actual, amb o sense un missatge\n" -"d'advertència.\n" +"per a permetre suprimir la branca actual, amb un missatge\n" +"d'advertència o sense.\n" "\n" "Per a silenciar aquest missatge, podeu establir-la a 'refuse'." #: builtin/receive-pack.c:1883 msgid "quiet" -msgstr "callat" +msgstr "silenciós" #: builtin/receive-pack.c:1897 msgid "You must specify a directory." @@ -11164,7 +11183,7 @@ msgid "" "--mirror is dangerous and deprecated; please\n" "\t use --mirror=fetch or --mirror=push instead" msgstr "" -"--mirror és perillós i desaprovat; si us\n" +"--mirror és perillós i està en desús; si us\n" "\t plau, useu --mirror=fetch o\n" "\t --mirror=push en lloc d'això" @@ -11311,17 +11330,17 @@ msgstr " ???" #: builtin/remote.c:955 #, c-format msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch" -msgstr "branch.%s.merge no vàlid; no es pot rebasar sobre > 1 branca" +msgstr "branch.%s.merge no vàlid; no es pot fer «rebase» sobre > 1 branca" #: builtin/remote.c:963 #, c-format msgid "rebases interactively onto remote %s" -msgstr "es rebasa interactivament sobre el remot %s" +msgstr "es fa «rebase» interactivament sobre el remot %s" #: builtin/remote.c:964 #, c-format msgid "rebases onto remote %s" -msgstr "es rebasa sobre el remot %s" +msgstr "es fa «rebase» sobre el remot %s" #: builtin/remote.c:967 #, c-format @@ -11668,7 +11687,7 @@ msgstr "--keep-unreachable i -A són incompatibles" #: builtin/repack.c:391 builtin/worktree.c:115 #, c-format msgid "failed to remove '%s'" -msgstr "s'ha fallat en eliminar '%s'" +msgstr "s'ha produït un error en eliminar '%s'" #: builtin/replace.c:19 msgid "git replace [-f] " @@ -11804,12 +11823,12 @@ msgstr "No teniu un HEAD vàlid." #: builtin/reset.c:76 msgid "Failed to find tree of HEAD." -msgstr "S'ha fallat en trobar l'arbre de HEAD." +msgstr "S'ha produït un error en trobar l'arbre de HEAD." #: builtin/reset.c:82 #, c-format msgid "Failed to find tree of %s." -msgstr "S'ha fallat en trobar l'arbre de %s." +msgstr "S'ha produït un error en cercar l'arbre de %s." #: builtin/reset.c:100 #, c-format @@ -11823,7 +11842,7 @@ msgstr "No es pot fer un restabliment de %s enmig d'una fusió." #: builtin/reset.c:276 msgid "be quiet, only report errors" -msgstr "calla, només informa d'errors" +msgstr "sigues silenciós, només informa d'errors" #: builtin/reset.c:278 msgid "reset HEAD and index" @@ -11848,12 +11867,12 @@ msgstr "registra només el fet de que els camins eliminats s'afegiran després" #: builtin/reset.c:305 #, c-format msgid "Failed to resolve '%s' as a valid revision." -msgstr "S'ha fallat en resoldre '%s' com a revisió vàlida." +msgstr "S'ha produït un error en resoldre '%s' com a revisió vàlida." #: builtin/reset.c:313 #, c-format msgid "Failed to resolve '%s' as a valid tree." -msgstr "S'ha fallat en resoldre '%s' com a arbre vàlid." +msgstr "S'ha produït un error en resoldre '%s' com a arbre vàlid." #: builtin/reset.c:322 msgid "--patch is incompatible with --{hard,mixed,soft}" @@ -11862,7 +11881,7 @@ msgstr "--patch és incompatible amb --{hard,mixed,soft}" #: builtin/reset.c:331 msgid "--mixed with paths is deprecated; use 'git reset -- ' instead." msgstr "" -"--mixed amb camins està desaprovat; useu 'git reset -- ' en lloc " +"--mixed amb camins està en desús; useu 'git reset -- ' en lloc " "d'això." #: builtin/reset.c:333 @@ -11950,15 +11969,15 @@ msgstr "%s: %s no es pot usar amb %s" #: builtin/revert.c:80 msgid "end revert or cherry-pick sequence" -msgstr "acaba la seqüència de reversió o el recull de cireres" +msgstr "acaba la seqüència de reversió o el «cherry pick»" #: builtin/revert.c:81 msgid "resume revert or cherry-pick sequence" -msgstr "reprèn la seqüència de reversió o el recull de cireres" +msgstr "reprèn la seqüència de reversió o el «cherry pick»" #: builtin/revert.c:82 msgid "cancel revert or cherry-pick sequence" -msgstr "cancel·la la seqüència de reversió o el recull de cireres" +msgstr "cancel·la la seqüència de reversió o el «cherry pick»" #: builtin/revert.c:83 msgid "don't automatically commit" @@ -12006,7 +12025,7 @@ msgstr "la reversió ha fallat" #: builtin/revert.c:205 msgid "cherry-pick failed" -msgstr "el recull de cireres ha fallat" +msgstr "el «cherry pick» ha fallat" #: builtin/rm.c:17 msgid "git rm [] [--] ..." @@ -12326,7 +12345,7 @@ msgstr "mostra la referència HEAD, encara que es filtrés" #: builtin/show-ref.c:174 msgid "dereference tags into object IDs" -msgstr "dereferencia les etiquetes a IDs d'objecte" +msgstr "desreferencia les etiquetes a IDs d'objecte" #: builtin/show-ref.c:176 msgid "only show SHA1 hash using digits" @@ -12351,7 +12370,7 @@ msgstr "git stripspace [-c | --comment-lines]" #: builtin/stripspace.c:35 msgid "skip and remove all lines starting with comment character" msgstr "" -"salta i elimina totes les línies començant amb el caràcter de comentari" +"omet i elimina totes les línies que comencin amb el caràcter de comentari" #: builtin/stripspace.c:38 msgid "prepend comment character and space to each line" @@ -12389,7 +12408,7 @@ msgstr "No s'ha trobat cap url per al camí de submòdul '%s' a .gitmodules" #: builtin/submodule--helper.c:369 #, c-format msgid "Failed to register url for submodule path '%s'" -msgstr "S'ha fallat en registrar l'url per al camí de submòdul '%s'" +msgstr "S'ha produït un error en registrar l'url per al camí de submòdul '%s'" #: builtin/submodule--helper.c:373 #, c-format @@ -12407,7 +12426,7 @@ msgstr "" #, c-format msgid "Failed to register update mode for submodule path '%s'" msgstr "" -"S'ha fallat en registrar el mode d'actualització per al camí de submòdul '%s'" +"S'ha produït un error en registrar el mode d'actualització per al camí de submòdul '%s'" #: builtin/submodule--helper.c:409 msgid "Suppress output for initializing a submodule" @@ -12506,22 +12525,22 @@ msgstr "Potser voleu usar 'update --init'?" #: builtin/submodule--helper.c:756 #, c-format msgid "Skipping unmerged submodule %s" -msgstr "S'està saltant el submòdul no fusionat %s" +msgstr "S'està ometent el submòdul no fusionat %s" #: builtin/submodule--helper.c:777 #, c-format msgid "Skipping submodule '%s'" -msgstr "S'està saltant el submòdul '%s'" +msgstr "S'està ometent el submòdul '%s'" #: builtin/submodule--helper.c:913 #, c-format msgid "Failed to clone '%s'. Retry scheduled" -msgstr "S'ha fallat en clonar '%s'. S'ha programat un reintent" +msgstr "S'ha produït un error en clonar '%s'. S'ha programat un reintent" #: builtin/submodule--helper.c:924 #, c-format msgid "Failed to clone '%s' a second time, aborting" -msgstr "S'ha fallat una segona vegada en clonar '%s', s'està avortant" +msgstr "S'ha produït un error per segon cop en clonar '%s', s'està avortant" #: builtin/submodule--helper.c:945 msgid "path into the working tree" @@ -12816,27 +12835,27 @@ msgstr "S'estan desempaquetant els objectes" #: builtin/update-index.c:79 #, c-format msgid "failed to create directory %s" -msgstr "s'ha fallat en crear el directori %s" +msgstr "s'ha produït un error en crear el directori %s" #: builtin/update-index.c:85 #, c-format msgid "failed to stat %s" -msgstr "s'ha fallat en fer stat a %s" +msgstr "s'ha produït un error en fer stat a %s" #: builtin/update-index.c:95 #, c-format msgid "failed to create file %s" -msgstr "s'ha fallat en crear el fitxer %s" +msgstr "s'ha produït un error en crear el fitxer %s" #: builtin/update-index.c:103 #, c-format msgid "failed to delete file %s" -msgstr "s'ha fallat en suprimir el fitxer %s" +msgstr "s'ha produït un error en suprimir el fitxer %s" #: builtin/update-index.c:110 builtin/update-index.c:212 #, c-format msgid "failed to delete directory %s" -msgstr "s'ha fallat en suprimir el directori %s" +msgstr "s'ha produït un error en suprimir el directori %s" #: builtin/update-index.c:133 #, c-format @@ -13233,7 +13252,7 @@ msgstr "surt després d'un sol intercanvi de sol·licitud/resposta" #: upload-pack.c:1030 msgid "exit immediately after initial ref advertisement" -msgstr "surt immediatament després del anunci inicial de referència" +msgstr "surt immediatament després de l'anunci inicial de referència" #: upload-pack.c:1032 msgid "do not try /.git/ if is no Git directory" @@ -13253,7 +13272,7 @@ msgid "" "\tchmod 0700 %s" msgstr "" "Els permisos en el vostre directori de sòcol són massa liberals;\n" -"potser que altres usuaris poden llegir els vostres credencials.\n" +"pot ser que altres usuaris poden llegir les vostres credencials.\n" "Considereu executar:\n" "\n" "\tchmod 0700 %s" @@ -13422,7 +13441,7 @@ msgstr "Cal començar per \"git bisect start\"" #. at this point. #: git-bisect.sh:60 msgid "Do you want me to do it for you [Y/n]? " -msgstr "Voleu que ho faci per vós [Y/n]? " +msgstr "Voleu que ho faci per vosté [Y/n]? " #: git-bisect.sh:121 #, sh-format @@ -13462,17 +13481,17 @@ msgstr "Paràmetre bisect_write dolent: $state" #: git-bisect.sh:262 #, sh-format msgid "Bad rev input: $arg" -msgstr "Introducció de revisió dolenta: $arg" +msgstr "Introducció de revisió errònia: $arg" #: git-bisect.sh:281 #, sh-format msgid "Bad rev input: $bisected_head" -msgstr "Entrada de revisió dolenta: $bisected_head" +msgstr "Entrada de revisió errònia: $bisected_head" #: git-bisect.sh:290 #, sh-format msgid "Bad rev input: $rev" -msgstr "Introducció de revisió dolenta: $rev" +msgstr "Introducció de revisió errònia: $rev" #: git-bisect.sh:299 #, sh-format @@ -13567,7 +13586,7 @@ msgid "" "'bisect_state $state' exited with error code $res" msgstr "" "el pas de bisecció ha fallat:\n" -"'bisect_state $state' ha surt amb el codi d'error $res" +"'bisect_state $state' ha surtit amb el codi d'error $res" #: git-bisect.sh:538 msgid "bisect run success" @@ -13660,9 +13679,9 @@ msgid "" "\"." msgstr "" "Quan hàgiu resolt aquest problema, executeu \"git rebase --continue\".\n" -"Si preferiu saltar aquest pedaç, executeu \"git rebase --skip\" en lloc " +"Si preferiu ometre aquest pedaç, executeu \"git rebase --skip\" en lloc " "d'això.\n" -"Per a agafar la branca original i deixar de rebasar, executeu \"git rebase --" +"Per a agafar la branca original i deixar de fer «rebase», executeu \"git rebase --" "abort\"." #: git-rebase.sh:156 git-rebase.sh:395 @@ -13691,11 +13710,11 @@ msgstr "" #: git-rebase.sh:210 msgid "The pre-rebase hook refused to rebase." -msgstr "El ganxo de prerebasament ha refusat rebasar." +msgstr "El ganxo pre-«rebase» ha refusat a fer «rebase»." #: git-rebase.sh:215 msgid "It looks like git-am is in progress. Cannot rebase." -msgstr "Sembla que git-am està en curs. No es pot rebasar." +msgstr "Sembla que git-am està en curs. No es pot fer «rebase»." #: git-rebase.sh:356 msgid "No rebase in progress?" @@ -13704,7 +13723,7 @@ msgstr "No hi ha rebasament en curs?" #: git-rebase.sh:367 msgid "The --edit-todo action can only be used during interactive rebase." msgstr "" -"L'acció --edit-todo només es pot usar durant un rebasament interactiva." +"L'acció --edit-todo només es pot usar durant un «rebase» interactiu." #: git-rebase.sh:374 msgid "Cannot read HEAD" @@ -13785,7 +13804,7 @@ msgstr "La branca actual $branch_name està al dia." #: git-rebase.sh:590 #, sh-format msgid "Current branch $branch_name is up to date, rebase forced." -msgstr "La branca actual $branch_name està al dia; rebasament forçada." +msgstr "La branca actual $branch_name està al dia; «rebase» forçat." #: git-rebase.sh:601 #, sh-format @@ -13878,11 +13897,11 @@ msgstr "No es pot desar l'estat actual" #: git-stash.sh:268 #, sh-format msgid "Saved working directory and index state $stash_msg" -msgstr "S'han desat el directori de treball i l'estat d'ìndex $stash_msg" +msgstr "S'han desat el directori de treball i l'estat d'índex $stash_msg" #: git-stash.sh:285 msgid "Cannot remove worktree changes" -msgstr "No es pot eliminar els canvis de l'arbre de treball" +msgstr "No es poden eliminar els canvis de l'arbre de treball" #: git-stash.sh:403 #, sh-format @@ -14037,12 +14056,12 @@ msgstr "no s'ha pogut agafar el submòdul '$sm_path'" #: git-submodule.sh:268 #, sh-format msgid "Failed to add submodule '$sm_path'" -msgstr "S'ha fallat en afegir el submòdul '$sm_path'" +msgstr "S'ha produït un error en afegir el submòdul '$sm_path'" #: git-submodule.sh:277 #, sh-format msgid "Failed to register submodule '$sm_path'" -msgstr "S'ha fallat en registrar el submòdul '$sm_path'" +msgstr "s'ha produït un error en registrar el submòdul '$sm_path'" #: git-submodule.sh:324 #, sh-format @@ -14150,12 +14169,12 @@ msgstr "Camí de submòdul '$displaypath': s'ha agafat '$sha1'" #: git-submodule.sh:668 #, sh-format msgid "Unable to rebase '$sha1' in submodule path '$displaypath'" -msgstr "no s'ha pogut rebasar '$sha1' en el camí de submòdul '$displaypath'" +msgstr "no s'ha pogut fer «rebase» '$sha1' en el camí de submòdul '$displaypath'" #: git-submodule.sh:669 #, sh-format msgid "Submodule path '$displaypath': rebased into '$sha1'" -msgstr "Camí de submòdul '$displaypath': s'ha rebasat en '$sha1'" +msgstr "Camí de submòdul '$displaypath': s'ha fet «rebase» en '$sha1'" #: git-submodule.sh:674 #, sh-format @@ -14182,7 +14201,7 @@ msgstr "Camí de submòdul '$displaypath': '$command $sha1'" #: git-submodule.sh:712 #, sh-format msgid "Failed to recurse into submodule path '$displaypath'" -msgstr "S'ha fallat en recursar al camí de submòdul '$displaypath'" +msgstr "s'ha produït un error en recorre recursivament dins del camí de submòdul '$displaypath'" #: git-submodule.sh:820 msgid "The --cached option cannot be used with the --files option" @@ -14211,7 +14230,7 @@ msgstr " Avís: $display_name no conté les comissions $sha1_src i $sha1_dst" #: git-submodule.sh:1045 #, sh-format msgid "Failed to recurse into submodule path '$sm_path'" -msgstr "S'ha fallat en recursar al camí de submòdul '$sm_path'" +msgstr "S'ha produït un error en recursar al camí de submòdul '$sm_path'" #: git-submodule.sh:1112 #, sh-format @@ -14226,7 +14245,7 @@ msgstr "Vegeu git-${cmd}(1) per detalls." #: git-rebase--interactive.sh:140 #, sh-format msgid "Rebasing ($new_count/$total)" -msgstr "S'està rebasant ($new_count/$total)" +msgstr "S'està fent «rebase» ($new_count/$total)" #: git-rebase--interactive.sh:156 msgid "" @@ -14246,7 +14265,7 @@ msgstr "" "Ordres:\n" " p, pick = usa la comissió\n" " r, reword = usa la comissió, però edita el missatge de comissió\n" -" e, edit = usa la commissió, però atura't per a esmenar\n" +" e, edit = usa la comissió, però atura't per a esmenar\n" " s, squash = usa la comissió, però fusiona'l a la comissió prèvia\n" " f, fixup = com \"squash\", però descarta el missatge de registre d'aquesta " "comissió\n" @@ -14295,7 +14314,7 @@ msgstr "" #: git-rebase--interactive.sh:236 #, sh-format msgid "$sha1: not a commit that can be picked" -msgstr "$sha1: no és una comissió que es pugi escollir" +msgstr "$sha1: no és una comissió que es pugui escollir" #: git-rebase--interactive.sh:275 #, sh-format @@ -14344,7 +14363,7 @@ msgstr "Aquest és el missatge de comissió núm. ${n}:" #: git-rebase--interactive.sh:421 #, sh-format msgid "The commit message #${n} will be skipped:" -msgstr "El missatge de comissió núm. ${n} se saltarà:" +msgstr "El missatge de comissió núm. ${n} s'ometrà:" #: git-rebase--interactive.sh:432 #, sh-format @@ -14364,7 +14383,7 @@ msgstr "Això és una combinació de 2 comissions." #: git-rebase--interactive.sh:444 msgid "This is the 1st commit message:" -msgstr "Aquest és el 1er missatge de comissió:" +msgstr "Aquest és el 1r missatge de comissió:" #: git-rebase--interactive.sh:484 git-rebase--interactive.sh:527 #: git-rebase--interactive.sh:530 @@ -14384,7 +14403,7 @@ msgstr "" "No s'ha pogut esmenar la comissió després d'escollir amb èxit $sha1... " "$rest\n" "Això és probablement a causa d'un missatge de comissió buit, o el ganxo de\n" -"precomissió ha fallat. Si el ganxo de precomissió ha fallat, potser que\n" +"precomissió ha fallat. Si el ganxo de precomissió ha fallat, pot ser que\n" "necessiteu resoldre el problema abans que pugueu canviar el missatge de\n" "comissió." @@ -14454,7 +14473,7 @@ msgstr "S'ha rebasat i actualitzat $head_name amb èxit." #: git-rebase--interactive.sh:749 msgid "Could not skip unnecessary pick commands" -msgstr "No s'ha pogut saltar ordres innecessaris d'elecció" +msgstr "No s'ha pogut ometre ordres innecessaris d'elecció" #: git-rebase--interactive.sh:907 #, sh-format @@ -14516,7 +14535,7 @@ msgid "" "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --" "continue'." msgstr "" -"Podeu arreglar això amb 'git rebase --edit-todo' i desprès 'git rebase --" +"Podeu arreglar això amb 'git rebase --edit-todo' i després 'git rebase --" "continue'." #: git-rebase--interactive.sh:1054 @@ -14554,14 +14573,14 @@ msgstr "" "\n" " git commit $gpg_sign_opt_quoted\n" "\n" -"En ambdós cassos, quan hàgiu terminat, continueu amb:\n" +"En ambdós casos, quan hàgiu terminat, continueu amb:\n" "\n" " git rebase --continue\n" #: git-rebase--interactive.sh:1100 msgid "Error trying to find the author identity to amend commit" msgstr "" -"Ha hagut un error en intentar trobar la identitat d'autor per a esmenar la " +"Hi ha hagut un error en intentar trobar la identitat d'autor per a esmenar la " "comissió" #: git-rebase--interactive.sh:1105 @@ -14664,7 +14683,7 @@ msgstr "No es pot reescriure branques: Teniu canvis no allistats." #: git-sh-setup.sh:226 msgid "Cannot pull with rebase: You have unstaged changes." -msgstr "No es pot baixar amb rebasament: Teniu canvis no allistats." +msgstr "No es pot baixar amb fent «rebase»: Teniu canvis no allistats." #: git-sh-setup.sh:229 #, sh-format @@ -14673,12 +14692,12 @@ msgstr "No es pot $action: Teniu canvis no allistats." #: git-sh-setup.sh:242 msgid "Cannot rebase: Your index contains uncommitted changes." -msgstr "No es pot rebasar: El vostre índex conté canvis sense cometre." +msgstr "No es pot fer «rebase»: El vostre índex conté canvis sense cometre." #: git-sh-setup.sh:245 msgid "Cannot pull with rebase: Your index contains uncommitted changes." msgstr "" -"No es pot baixar amb rebasament: El vostre índex conté canvis sense cometre." +"No es pot baixar amb fent «rebase»: El vostre índex conté canvis sense cometre." #: git-sh-setup.sh:248 #, sh-format From c5f3cba1266dc4feb4101f85e5596912fdb10d5b Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 3 Jan 2017 10:30:47 -0800 Subject: [PATCH 0135/1540] submodule.c: use GIT_DIR_ENVIRONMENT consistently In C code we have the luxury of having constants for all the important things that are hard coded. This is the only place in C that hard codes the git directory environment variable, so fix it. Signed-off-by: Stefan Beller Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- submodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/submodule.c b/submodule.c index ece17315d671cf..fa32f450331a8a 100644 --- a/submodule.c +++ b/submodule.c @@ -1333,5 +1333,6 @@ void prepare_submodule_repo_env(struct argv_array *out) if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) argv_array_push(out, *var); } - argv_array_push(out, "GIT_DIR=.git"); + argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT, + DEFAULT_GIT_DIR_ENVIRONMENT); } From c7cf956618c4af9f3151a90d7552b05767e6444f Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 4 Jan 2017 01:27:07 +0530 Subject: [PATCH 0136/1540] don't use test_must_fail with grep test_must_fail should only be used for testing git commands. To test the failure of other commands use `!`. Reported-by: Stefan Beller Signed-off-by: Pranit Bauva Signed-off-by: Junio C Hamano --- t/t3510-cherry-pick-sequence.sh | 6 +++--- t/t5504-fetch-receive-strict.sh | 2 +- t/t5516-fetch-push.sh | 2 +- t/t5601-clone.sh | 2 +- t/t6030-bisect-porcelain.sh | 2 +- t/t7610-mergetool.sh | 2 +- t/t9001-send-email.sh | 2 +- t/t9117-git-svn-init-clone.sh | 12 ++++++------ t/t9813-git-p4-preserve-users.sh | 8 ++++---- t/t9814-git-p4-rename.sh | 6 +++--- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 7b7a89dbd5ce57..362448742e767a 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -375,7 +375,7 @@ test_expect_success '--continue respects opts' ' git cat-file commit HEAD~1 >picked_msg && git cat-file commit HEAD~2 >unrelatedpick_msg && git cat-file commit HEAD~3 >initial_msg && - test_must_fail grep "cherry picked from" initial_msg && + ! grep "cherry picked from" initial_msg && grep "cherry picked from" unrelatedpick_msg && grep "cherry picked from" picked_msg && grep "cherry picked from" anotherpick_msg @@ -416,9 +416,9 @@ test_expect_failure '--signoff is automatically propagated to resolved conflict' git cat-file commit HEAD~1 >picked_msg && git cat-file commit HEAD~2 >unrelatedpick_msg && git cat-file commit HEAD~3 >initial_msg && - test_must_fail grep "Signed-off-by:" initial_msg && + ! grep "Signed-off-by:" initial_msg && grep "Signed-off-by:" unrelatedpick_msg && - test_must_fail grep "Signed-off-by:" picked_msg && + ! grep "Signed-off-by:" picked_msg && grep "Signed-off-by:" anotherpick_msg ' diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 9b19cff729381b..49d3621a926786 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -152,7 +152,7 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' ' git --git-dir=dst/.git config --add \ receive.fsck.badDate warn && git push --porcelain dst bogus >act 2>&1 && - test_must_fail grep "missingEmail" act + ! grep "missingEmail" act ' test_expect_success \ diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 26b2cafc4795ba..0fc5a7c596b5d5 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1004,7 +1004,7 @@ test_expect_success 'push --porcelain' ' test_expect_success 'push --porcelain bad url' ' mk_empty testrepo && test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master && - test_must_fail grep -q Done .git/bar + ! grep -q Done .git/bar ' test_expect_success 'push --porcelain rejected' ' diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index a4333942001971..4241ea5b32db4a 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -151,7 +151,7 @@ test_expect_success 'clone --mirror does not repeat tags' ' git clone --mirror src mirror2 && (cd mirror2 && git show-ref 2> clone.err > clone.out) && - test_must_fail grep Duplicate mirror2/clone.err && + ! grep Duplicate mirror2/clone.err && grep some-tag mirror2/clone.out ' diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 5e5370feb40c85..8c2c6eaef83fe9 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -407,7 +407,7 @@ test_expect_success 'good merge base when good and bad are siblings' ' test_i18ngrep "merge base must be tested" my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && git bisect good > my_bisect_log.txt && - test_must_fail grep "merge base must be tested" my_bisect_log.txt && + ! grep "merge base must be tested" my_bisect_log.txt && grep $HASH6 my_bisect_log.txt && git bisect reset ' diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 6d9f21511fe106..caf907b297d36e 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -601,7 +601,7 @@ test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToT test_config mergetool.myecho.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && - test_must_fail grep ^\./both_LOCAL_ actual >/dev/null && + ! grep ^\./both_LOCAL_ actual >/dev/null && grep /both_LOCAL_ actual >/dev/null && git reset --hard master >/dev/null 2>&1 ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 3dc4a3454d223d..0f398dd1603d94 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -50,7 +50,7 @@ test_no_confirm () { --smtp-server="$(pwd)/fake.sendmail" \ $@ \ $patches >stdout && - test_must_fail grep "Send this email" stdout && + ! grep "Send this email" stdout && >no_confirm_okay } diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh index 69a675052e2099..044f65e91660b7 100755 --- a/t/t9117-git-svn-init-clone.sh +++ b/t/t9117-git-svn-init-clone.sh @@ -55,7 +55,7 @@ test_expect_success 'clone to target directory with --stdlayout' ' test_expect_success 'init without -s/-T/-b/-t does not warn' ' test ! -d trunk && git svn init "$svnrepo"/project/trunk trunk 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && rm -rf trunk && rm -f warning ' @@ -63,7 +63,7 @@ test_expect_success 'init without -s/-T/-b/-t does not warn' ' test_expect_success 'clone without -s/-T/-b/-t does not warn' ' test ! -d trunk && git svn clone "$svnrepo"/project/trunk 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && rm -rf trunk && rm -f warning ' @@ -86,7 +86,7 @@ EOF test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' ' test ! -d project && git svn init -s "$svnrepo"/project project 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "origin/" && rm -rf project && rm -f warning @@ -95,7 +95,7 @@ test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' ' test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' ' test ! -d project && git svn clone -s "$svnrepo"/project 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "origin/" && rm -rf project && rm -f warning @@ -104,7 +104,7 @@ test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' ' test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' ' test ! -d project && git svn init -s "$svnrepo"/project project --prefix "" 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "" && rm -rf project && rm -f warning @@ -113,7 +113,7 @@ test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' ' test_expect_success 'clone with -s/-T/-b/-t and --prefix "" still works' ' test ! -d project && git svn clone -s "$svnrepo"/project --prefix "" 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "" && rm -rf project && rm -f warning diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh index 0fe23128070745..76004a5ad667ea 100755 --- a/t/t9813-git-p4-preserve-users.sh +++ b/t/t9813-git-p4-preserve-users.sh @@ -126,13 +126,13 @@ test_expect_success 'not preserving user with mixed authorship' ' grep "git author charlie@example.com does not match" && make_change_by_user usernamefile3 alice alice@example.com && - git p4 commit |\ - test_must_fail grep "git author.*does not match" && + git p4 commit >actual && + ! grep "git author.*does not match" actual && git config git-p4.skipUserNameCheck true && make_change_by_user usernamefile3 Charlie charlie@example.com && - git p4 commit |\ - test_must_fail grep "git author.*does not match" && + git p4 commit >actual && + ! grep "git author.*does not match" actual && p4_check_commit_author usernamefile3 alice ) diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index c89992cf95c7fa..e7e0268e985072 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -141,7 +141,7 @@ test_expect_success 'detect copies' ' git diff-tree -r -C HEAD && git p4 submit && p4 filelog //depot/file8 && - p4 filelog //depot/file8 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file8 | grep -q "branch from" && echo "file9" >>file2 && git commit -a -m "Differentiate file2" && @@ -154,7 +154,7 @@ test_expect_success 'detect copies' ' git config git-p4.detectCopies true && git p4 submit && p4 filelog //depot/file9 && - p4 filelog //depot/file9 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file9 | grep -q "branch from" && echo "file10" >>file2 && git commit -a -m "Differentiate file2" && @@ -202,7 +202,7 @@ test_expect_success 'detect copies' ' git config git-p4.detectCopies $(($level + 2)) && git p4 submit && p4 filelog //depot/file12 && - p4 filelog //depot/file12 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file12 | grep -q "branch from" && echo "file13" >>file2 && git commit -a -m "Differentiate file2" && From 3c87878490613c0bb8bd5a49fdafd57d388bf6a6 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Wed, 28 Dec 2016 09:28:37 -0800 Subject: [PATCH 0137/1540] contrib: remove gitview gitview did not have meaningful contributions since 2007, which gives the impression it is either a mature or dead project. In both cases we should not carry it in git.git as the README for contrib states we only want to carry experimental things to give early exposure. Recently a security vulnerability was reported by Javantea, so the decision to either fix the issue or remove the code in question becomes a bit more urgent. Reported-by: Javantea Signed-off-by: Stefan Beller Acked-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano --- contrib/gitview/gitview | 1305 ----------------------------------- contrib/gitview/gitview.txt | 57 -- 2 files changed, 1362 deletions(-) delete mode 100755 contrib/gitview/gitview delete mode 100644 contrib/gitview/gitview.txt diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview deleted file mode 100755 index 4e23c650fe6341..00000000000000 --- a/contrib/gitview/gitview +++ /dev/null @@ -1,1305 +0,0 @@ -#! /usr/bin/env python - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. - -""" gitview -GUI browser for git repository -This program is based on bzrk by Scott James Remnant -""" -__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P." -__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V .*) (?P\d+) (?P[+-]\d{4})') - -def list_to_string(args, skip): - count = len(args) - i = skip - str_arg=" " - while (i < count ): - str_arg = str_arg + args[i] - str_arg = str_arg + " " - i = i+1 - - return str_arg - -def show_date(epoch, tz): - secs = float(epoch) - tzsecs = float(tz[1:3]) * 3600 - tzsecs += float(tz[3:5]) * 60 - if (tz[0] == "+"): - secs += tzsecs - else: - secs -= tzsecs - - return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs)) - -def get_source_buffer_and_view(): - if have_gtksourceview2: - buffer = gtksourceview2.Buffer() - slm = gtksourceview2.LanguageManager() - gsl = slm.get_language("diff") - buffer.set_highlight_syntax(True) - buffer.set_language(gsl) - view = gtksourceview2.View(buffer) - elif have_gtksourceview: - buffer = gtksourceview.SourceBuffer() - slm = gtksourceview.SourceLanguagesManager() - gsl = slm.get_language_from_mime_type("text/x-patch") - buffer.set_highlight(True) - buffer.set_language(gsl) - view = gtksourceview.SourceView(buffer) - else: - buffer = gtk.TextBuffer() - view = gtk.TextView(buffer) - return (buffer, view) - - -class CellRendererGraph(gtk.GenericCellRenderer): - """Cell renderer for directed graph. - - This module contains the implementation of a custom GtkCellRenderer that - draws part of the directed graph based on the lines suggested by the code - in graph.py. - - Because we're shiny, we use Cairo to do this, and because we're naughty - we cheat and draw over the bits of the TreeViewColumn that are supposed to - just be for the background. - - Properties: - node (column, colour, [ names ]) tuple to draw revision node, - in_lines (start, end, colour) tuple list to draw inward lines, - out_lines (start, end, colour) tuple list to draw outward lines. - """ - - __gproperties__ = { - "node": ( gobject.TYPE_PYOBJECT, "node", - "revision node instruction", - gobject.PARAM_WRITABLE - ), - "in-lines": ( gobject.TYPE_PYOBJECT, "in-lines", - "instructions to draw lines into the cell", - gobject.PARAM_WRITABLE - ), - "out-lines": ( gobject.TYPE_PYOBJECT, "out-lines", - "instructions to draw lines out of the cell", - gobject.PARAM_WRITABLE - ), - } - - def do_set_property(self, property, value): - """Set properties from GObject properties.""" - if property.name == "node": - self.node = value - elif property.name == "in-lines": - self.in_lines = value - elif property.name == "out-lines": - self.out_lines = value - else: - raise AttributeError, "no such property: '%s'" % property.name - - def box_size(self, widget): - """Calculate box size based on widget's font. - - Cache this as it's probably expensive to get. It ensures that we - draw the graph at least as large as the text. - """ - try: - return self._box_size - except AttributeError: - pango_ctx = widget.get_pango_context() - font_desc = widget.get_style().font_desc - metrics = pango_ctx.get_metrics(font_desc) - - ascent = pango.PIXELS(metrics.get_ascent()) - descent = pango.PIXELS(metrics.get_descent()) - - self._box_size = ascent + descent + 6 - return self._box_size - - def set_colour(self, ctx, colour, bg, fg): - """Set the context source colour. - - Picks a distinct colour based on an internal wheel; the bg - parameter provides the value that should be assigned to the 'zero' - colours and the fg parameter provides the multiplier that should be - applied to the foreground colours. - """ - colours = [ - ( 1.0, 0.0, 0.0 ), - ( 1.0, 1.0, 0.0 ), - ( 0.0, 1.0, 0.0 ), - ( 0.0, 1.0, 1.0 ), - ( 0.0, 0.0, 1.0 ), - ( 1.0, 0.0, 1.0 ), - ] - - colour %= len(colours) - red = (colours[colour][0] * fg) or bg - green = (colours[colour][1] * fg) or bg - blue = (colours[colour][2] * fg) or bg - - ctx.set_source_rgb(red, green, blue) - - def on_get_size(self, widget, cell_area): - """Return the size we need for this cell. - - Each cell is drawn individually and is only as wide as it needs - to be, we let the TreeViewColumn take care of making them all - line up. - """ - box_size = self.box_size(widget) - - cols = self.node[0] - for start, end, colour in self.in_lines + self.out_lines: - cols = int(max(cols, start, end)) - - (column, colour, names) = self.node - names_len = 0 - if (len(names) != 0): - for item in names: - names_len += len(item) - - width = box_size * (cols + 1 ) + names_len - height = box_size - - # FIXME I have no idea how to use cell_area properly - return (0, 0, width, height) - - def on_render(self, window, widget, bg_area, cell_area, exp_area, flags): - """Render an individual cell. - - Draws the cell contents using cairo, taking care to clip what we - do to within the background area so we don't draw over other cells. - Note that we're a bit naughty there and should really be drawing - in the cell_area (or even the exposed area), but we explicitly don't - want any gutter. - - We try and be a little clever, if the line we need to draw is going - to cross other columns we actually draw it as in the .---' style - instead of a pure diagonal ... this reduces confusion by an - incredible amount. - """ - ctx = window.cairo_create() - ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height) - ctx.clip() - - box_size = self.box_size(widget) - - ctx.set_line_width(box_size / 8) - ctx.set_line_cap(cairo.LINE_CAP_SQUARE) - - # Draw lines into the cell - for start, end, colour in self.in_lines: - ctx.move_to(cell_area.x + box_size * start + box_size / 2, - bg_area.y - bg_area.height / 2) - - if start - end > 1: - ctx.line_to(cell_area.x + box_size * start, bg_area.y) - ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y) - elif start - end < -1: - ctx.line_to(cell_area.x + box_size * start + box_size, - bg_area.y) - ctx.line_to(cell_area.x + box_size * end, bg_area.y) - - ctx.line_to(cell_area.x + box_size * end + box_size / 2, - bg_area.y + bg_area.height / 2) - - self.set_colour(ctx, colour, 0.0, 0.65) - ctx.stroke() - - # Draw lines out of the cell - for start, end, colour in self.out_lines: - ctx.move_to(cell_area.x + box_size * start + box_size / 2, - bg_area.y + bg_area.height / 2) - - if start - end > 1: - ctx.line_to(cell_area.x + box_size * start, - bg_area.y + bg_area.height) - ctx.line_to(cell_area.x + box_size * end + box_size, - bg_area.y + bg_area.height) - elif start - end < -1: - ctx.line_to(cell_area.x + box_size * start + box_size, - bg_area.y + bg_area.height) - ctx.line_to(cell_area.x + box_size * end, - bg_area.y + bg_area.height) - - ctx.line_to(cell_area.x + box_size * end + box_size / 2, - bg_area.y + bg_area.height / 2 + bg_area.height) - - self.set_colour(ctx, colour, 0.0, 0.65) - ctx.stroke() - - # Draw the revision node in the right column - (column, colour, names) = self.node - ctx.arc(cell_area.x + box_size * column + box_size / 2, - cell_area.y + cell_area.height / 2, - box_size / 4, 0, 2 * math.pi) - - - self.set_colour(ctx, colour, 0.0, 0.5) - ctx.stroke_preserve() - - self.set_colour(ctx, colour, 0.5, 1.0) - ctx.fill_preserve() - - if (len(names) != 0): - name = " " - for item in names: - name = name + item + " " - - ctx.set_font_size(13) - if (flags & 1): - self.set_colour(ctx, colour, 0.5, 1.0) - else: - self.set_colour(ctx, colour, 0.0, 0.5) - ctx.show_text(name) - -class Commit(object): - """ This represent a commit object obtained after parsing the git-rev-list - output """ - - __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer', - 'commit_date', 'commit_sha1', 'parent_sha1'] - - children_sha1 = {} - - def __init__(self, commit_lines): - self.message = "" - self.author = "" - self.date = "" - self.committer = "" - self.commit_date = "" - self.commit_sha1 = "" - self.parent_sha1 = [ ] - self.parse_commit(commit_lines) - - - def parse_commit(self, commit_lines): - - # First line is the sha1 lines - line = string.strip(commit_lines[0]) - sha1 = re.split(" ", line) - self.commit_sha1 = sha1[0] - self.parent_sha1 = sha1[1:] - - #build the child list - for parent_id in self.parent_sha1: - try: - Commit.children_sha1[parent_id].append(self.commit_sha1) - except KeyError: - Commit.children_sha1[parent_id] = [self.commit_sha1] - - # IF we don't have parent - if (len(self.parent_sha1) == 0): - self.parent_sha1 = [0] - - for line in commit_lines[1:]: - m = re.match("^ ", line) - if (m != None): - # First line of the commit message used for short log - if self.message == "": - self.message = string.strip(line) - continue - - m = re.match("tree", line) - if (m != None): - continue - - m = re.match("parent", line) - if (m != None): - continue - - m = re_ident.match(line) - if (m != None): - date = show_date(m.group('epoch'), m.group('tz')) - if m.group(1) == "author": - self.author = m.group('ident') - self.date = date - elif m.group(1) == "committer": - self.committer = m.group('ident') - self.commit_date = date - - continue - - def get_message(self, with_diff=0): - if (with_diff == 1): - message = self.diff_tree() - else: - fp = os.popen("git cat-file commit " + self.commit_sha1) - message = fp.read() - fp.close() - - return message - - def diff_tree(self): - fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1) - diff = fp.read() - fp.close() - return diff - -class AnnotateWindow(object): - """Annotate window. - This object represents and manages a single window containing the - annotate information of the file - """ - - def __init__(self): - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_border_width(0) - self.window.set_title("Git repository browser annotation window") - self.prev_read = "" - - # Use two thirds of the screen by default - screen = self.window.get_screen() - monitor = screen.get_monitor_geometry(0) - width = int(monitor.width * 0.66) - height = int(monitor.height * 0.66) - self.window.set_default_size(width, height) - - def add_file_data(self, filename, commit_sha1, line_num): - fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename) - i = 1; - for line in fp.readlines(): - line = string.rstrip(line) - self.model.append(None, ["HEAD", filename, line, i]) - i = i+1 - fp.close() - - # now set the cursor position - self.treeview.set_cursor(line_num-1) - self.treeview.grab_focus() - - def _treeview_cursor_cb(self, *args): - """Callback for when the treeview cursor changes.""" - (path, col) = self.treeview.get_cursor() - commit_sha1 = self.model[path][0] - commit_msg = "" - fp = os.popen("git cat-file commit " + commit_sha1) - for line in fp.readlines(): - commit_msg = commit_msg + line - fp.close() - - self.commit_buffer.set_text(commit_msg) - - def _treeview_row_activated(self, *args): - """Callback for when the treeview row gets selected.""" - (path, col) = self.treeview.get_cursor() - commit_sha1 = self.model[path][0] - filename = self.model[path][1] - line_num = self.model[path][3] - - window = AnnotateWindow(); - fp = os.popen("git rev-parse "+ commit_sha1 + "~1") - commit_sha1 = string.strip(fp.readline()) - fp.close() - window.annotate(filename, commit_sha1, line_num) - - def data_ready(self, source, condition): - while (1): - try : - # A simple readline doesn't work - # a readline bug ?? - buffer = source.read(100) - - except: - # resource temporary not available - return True - - if (len(buffer) == 0): - gobject.source_remove(self.io_watch_tag) - source.close() - return False - - if (self.prev_read != ""): - buffer = self.prev_read + buffer - self.prev_read = "" - - if (buffer[len(buffer) -1] != '\n'): - try: - newline_index = buffer.rindex("\n") - except ValueError: - newline_index = 0 - - self.prev_read = buffer[newline_index:(len(buffer))] - buffer = buffer[0:newline_index] - - for buff in buffer.split("\n"): - annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$') - m = annotate_line.match(buff) - if not m: - annotate_line = re.compile('^(filename) (.+)$') - m = annotate_line.match(buff) - if not m: - continue - filename = m.group(2) - else: - self.commit_sha1 = m.group(1) - self.source_line = int(m.group(2)) - self.result_line = int(m.group(3)) - self.count = int(m.group(4)) - #set the details only when we have the file name - continue - - while (self.count > 0): - # set at result_line + count-1 the sha1 as commit_sha1 - self.count = self.count - 1 - iter = self.model.iter_nth_child(None, self.result_line + self.count-1) - self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line) - - - def annotate(self, filename, commit_sha1, line_num): - # verify the commit_sha1 specified has this filename - - fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename) - line = string.strip(fp.readline()) - if line == '': - # pop up the message the file is not there as a part of the commit - fp.close() - dialog = gtk.MessageDialog(parent=None, flags=0, - type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE, - message_format=None) - dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1)) - dialog.run() - dialog.destroy() - return - - fp.close() - - vpan = gtk.VPaned(); - self.window.add(vpan); - vpan.show() - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vpan.pack1(scrollwin, True, True); - scrollwin.show() - - self.model = gtk.TreeStore(str, str, str, int) - self.treeview = gtk.TreeView(self.model) - self.treeview.set_rules_hint(True) - self.treeview.set_search_column(0) - self.treeview.connect("cursor-changed", self._treeview_cursor_cb) - self.treeview.connect("row-activated", self._treeview_row_activated) - scrollwin.add(self.treeview) - self.treeview.show() - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 10) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Commit") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 0) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 20) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("File Name") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 1) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 20) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Data") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 2) - self.treeview.append_column(column) - - # The commit message window - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vpan.pack2(scrollwin, True, True); - scrollwin.show() - - commit_text = gtk.TextView() - self.commit_buffer = gtk.TextBuffer() - commit_text.set_buffer(self.commit_buffer) - scrollwin.add(commit_text) - commit_text.show() - - self.window.show() - - self.add_file_data(filename, commit_sha1, line_num) - - fp = os.popen("git blame --incremental -C -C -- " + filename + " " + commit_sha1) - flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL) - fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK) - self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready) - - -class DiffWindow(object): - """Diff window. - This object represents and manages a single window containing the - differences between two revisions on a branch. - """ - - def __init__(self): - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_border_width(0) - self.window.set_title("Git repository browser diff window") - - # Use two thirds of the screen by default - screen = self.window.get_screen() - monitor = screen.get_monitor_geometry(0) - width = int(monitor.width * 0.66) - height = int(monitor.height * 0.66) - self.window.set_default_size(width, height) - - - self.construct() - - def construct(self): - """Construct the window contents.""" - vbox = gtk.VBox() - self.window.add(vbox) - vbox.show() - - menu_bar = gtk.MenuBar() - save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE) - save_menu.connect("activate", self.save_menu_response, "save") - save_menu.show() - menu_bar.append(save_menu) - vbox.pack_start(menu_bar, expand=False, fill=True) - menu_bar.show() - - hpan = gtk.HPaned() - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - hpan.pack1(scrollwin, True, True) - scrollwin.show() - - (self.buffer, sourceview) = get_source_buffer_and_view() - - sourceview.set_editable(False) - sourceview.modify_font(pango.FontDescription("Monospace")) - scrollwin.add(sourceview) - sourceview.show() - - # The file hierarchy: a scrollable treeview - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - scrollwin.set_size_request(20, -1) - hpan.pack2(scrollwin, True, True) - scrollwin.show() - - self.model = gtk.TreeStore(str, str, str) - self.treeview = gtk.TreeView(self.model) - self.treeview.set_search_column(1) - self.treeview.connect("cursor-changed", self._treeview_clicked) - scrollwin.add(self.treeview) - self.treeview.show() - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 20) - column = gtk.TreeViewColumn("Select to annotate") - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 0) - self.treeview.append_column(column) - - vbox.pack_start(hpan, expand=True, fill=True) - hpan.show() - - def _treeview_clicked(self, *args): - """Callback for when the treeview cursor changes.""" - (path, col) = self.treeview.get_cursor() - specific_file = self.model[path][1] - commit_sha1 = self.model[path][2] - if specific_file == None : - return - elif specific_file == "" : - specific_file = None - - window = AnnotateWindow(); - window.annotate(specific_file, commit_sha1, 1) - - - def commit_files(self, commit_sha1, parent_sha1): - self.model.clear() - add = self.model.append(None, [ "Added", None, None]) - dele = self.model.append(None, [ "Deleted", None, None]) - mod = self.model.append(None, [ "Modified", None, None]) - diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$') - fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1) - while 1: - line = string.strip(fp.readline()) - if line == '': - break - m = diff_tree.match(line) - if not m: - continue - - attr = m.group(5) - filename = m.group(6) - if attr == "A": - self.model.append(add, [filename, filename, commit_sha1]) - elif attr == "D": - self.model.append(dele, [filename, filename, commit_sha1]) - elif attr == "M": - self.model.append(mod, [filename, filename, commit_sha1]) - fp.close() - - self.treeview.expand_all() - - def set_diff(self, commit_sha1, parent_sha1, encoding): - """Set the differences showed by this window. - Compares the two trees and populates the window with the - differences. - """ - # Diff with the first commit or the last commit shows nothing - if (commit_sha1 == 0 or parent_sha1 == 0 ): - return - - fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1) - self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8')) - fp.close() - self.commit_files(commit_sha1, parent_sha1) - self.window.show() - - def save_menu_response(self, widget, string): - dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK)) - dialog.set_default_response(gtk.RESPONSE_OK) - response = dialog.run() - if response == gtk.RESPONSE_OK: - patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(), - self.buffer.get_end_iter()) - fp = open(dialog.get_filename(), "w") - fp.write(patch_buffer) - fp.close() - dialog.destroy() - -class GitView(object): - """ This is the main class - """ - version = "0.9" - - def __init__(self, with_diff=0): - self.with_diff = with_diff - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_border_width(0) - self.window.set_title("Git repository browser") - - self.get_encoding() - self.get_bt_sha1() - - # Use three-quarters of the screen by default - screen = self.window.get_screen() - monitor = screen.get_monitor_geometry(0) - width = int(monitor.width * 0.75) - height = int(monitor.height * 0.75) - self.window.set_default_size(width, height) - - # FIXME AndyFitz! - icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON) - self.window.set_icon(icon) - - self.accel_group = gtk.AccelGroup() - self.window.add_accel_group(self.accel_group) - self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh); - self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize); - self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen); - self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen); - - self.window.add(self.construct()) - - def refresh(self, widget, event=None, *arguments, **keywords): - self.get_encoding() - self.get_bt_sha1() - Commit.children_sha1 = {} - self.set_branch(sys.argv[without_diff:]) - self.window.show() - return True - - def maximize(self, widget, event=None, *arguments, **keywords): - self.window.maximize() - return True - - def fullscreen(self, widget, event=None, *arguments, **keywords): - self.window.fullscreen() - return True - - def unfullscreen(self, widget, event=None, *arguments, **keywords): - self.window.unfullscreen() - return True - - def get_bt_sha1(self): - """ Update the bt_sha1 dictionary with the - respective sha1 details """ - - self.bt_sha1 = { } - ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$'); - fp = os.popen('git ls-remote "${GIT_DIR-.git}"') - while 1: - line = string.strip(fp.readline()) - if line == '': - break - m = ls_remote.match(line) - if not m: - continue - (sha1, name) = (m.group(1), m.group(2)) - if not self.bt_sha1.has_key(sha1): - self.bt_sha1[sha1] = [] - self.bt_sha1[sha1].append(name) - fp.close() - - def get_encoding(self): - fp = os.popen("git config --get i18n.commitencoding") - self.encoding=string.strip(fp.readline()) - fp.close() - if (self.encoding == ""): - self.encoding = "utf-8" - - - def construct(self): - """Construct the window contents.""" - vbox = gtk.VBox() - paned = gtk.VPaned() - paned.pack1(self.construct_top(), resize=False, shrink=True) - paned.pack2(self.construct_bottom(), resize=False, shrink=True) - menu_bar = gtk.MenuBar() - menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL) - help_menu = gtk.MenuItem("Help") - menu = gtk.Menu() - about_menu = gtk.MenuItem("About") - menu.append(about_menu) - about_menu.connect("activate", self.about_menu_response, "about") - about_menu.show() - help_menu.set_submenu(menu) - help_menu.show() - menu_bar.append(help_menu) - menu_bar.show() - vbox.pack_start(menu_bar, expand=False, fill=True) - vbox.pack_start(paned, expand=True, fill=True) - paned.show() - vbox.show() - return vbox - - - def construct_top(self): - """Construct the top-half of the window.""" - vbox = gtk.VBox(spacing=6) - vbox.set_border_width(12) - vbox.show() - - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vbox.pack_start(scrollwin, expand=True, fill=True) - scrollwin.show() - - self.treeview = gtk.TreeView() - self.treeview.set_rules_hint(True) - self.treeview.set_search_column(4) - self.treeview.connect("cursor-changed", self._treeview_cursor_cb) - scrollwin.add(self.treeview) - self.treeview.show() - - cell = CellRendererGraph() - column = gtk.TreeViewColumn() - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "node", 1) - column.add_attribute(cell, "in-lines", 2) - column.add_attribute(cell, "out-lines", 3) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 65) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Message") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 4) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 40) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Author") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 5) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Date") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 6) - self.treeview.append_column(column) - - return vbox - - def about_menu_response(self, widget, string): - dialog = gtk.AboutDialog() - dialog.set_name("Gitview") - dialog.set_version(GitView.version) - dialog.set_authors(["Aneesh Kumar K.V "]) - dialog.set_website("http://www.kernel.org/pub/software/scm/git/") - dialog.set_copyright("Use and distribute under the terms of the GNU General Public License") - dialog.set_wrap_license(True) - dialog.run() - dialog.destroy() - - - def construct_bottom(self): - """Construct the bottom half of the window.""" - vbox = gtk.VBox(False, spacing=6) - vbox.set_border_width(12) - (width, height) = self.window.get_size() - vbox.set_size_request(width, int(height / 2.5)) - vbox.show() - - self.table = gtk.Table(rows=4, columns=4) - self.table.set_row_spacings(6) - self.table.set_col_spacings(6) - vbox.pack_start(self.table, expand=False, fill=True) - self.table.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("Revision:") - align.add(label) - self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL) - label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - self.revid_label = gtk.Label() - self.revid_label.set_selectable(True) - align.add(self.revid_label) - self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL) - self.revid_label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("Committer:") - align.add(label) - self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL) - label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - self.committer_label = gtk.Label() - self.committer_label.set_selectable(True) - align.add(self.committer_label) - self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL) - self.committer_label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("Timestamp:") - align.add(label) - self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL) - label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - self.timestamp_label = gtk.Label() - self.timestamp_label.set_selectable(True) - align.add(self.timestamp_label) - self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL) - self.timestamp_label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("Parents:") - align.add(label) - self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL) - label.show() - align.show() - self.parents_widgets = [] - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("Children:") - align.add(label) - self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL) - label.show() - align.show() - self.children_widgets = [] - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vbox.pack_start(scrollwin, expand=True, fill=True) - scrollwin.show() - - (self.message_buffer, sourceview) = get_source_buffer_and_view() - - sourceview.set_editable(False) - sourceview.modify_font(pango.FontDescription("Monospace")) - scrollwin.add(sourceview) - sourceview.show() - - return vbox - - def _treeview_cursor_cb(self, *args): - """Callback for when the treeview cursor changes.""" - (path, col) = self.treeview.get_cursor() - commit = self.model[path][0] - - if commit.committer is not None: - committer = commit.committer - timestamp = commit.commit_date - message = commit.get_message(self.with_diff) - revid_label = commit.commit_sha1 - else: - committer = "" - timestamp = "" - message = "" - revid_label = "" - - self.revid_label.set_text(revid_label) - self.committer_label.set_text(committer) - self.timestamp_label.set_text(timestamp) - self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8')) - - for widget in self.parents_widgets: - self.table.remove(widget) - - self.parents_widgets = [] - self.table.resize(4 + len(commit.parent_sha1) - 1, 4) - for idx, parent_id in enumerate(commit.parent_sha1): - self.table.set_row_spacing(idx + 3, 0) - - align = gtk.Alignment(0.0, 0.0) - self.parents_widgets.append(align) - self.table.attach(align, 1, 2, idx + 3, idx + 4, - gtk.EXPAND | gtk.FILL, gtk.FILL) - align.show() - - hbox = gtk.HBox(False, 0) - align.add(hbox) - hbox.show() - - label = gtk.Label(parent_id) - label.set_selectable(True) - hbox.pack_start(label, expand=False, fill=True) - label.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.connect("clicked", self._go_clicked_cb, parent_id) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.set_sensitive(True) - button.connect("clicked", self._show_clicked_cb, - commit.commit_sha1, parent_id, self.encoding) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - # Populate with child details - for widget in self.children_widgets: - self.table.remove(widget) - - self.children_widgets = [] - try: - child_sha1 = Commit.children_sha1[commit.commit_sha1] - except KeyError: - # We don't have child - child_sha1 = [ 0 ] - - if ( len(child_sha1) > len(commit.parent_sha1)): - self.table.resize(4 + len(child_sha1) - 1, 4) - - for idx, child_id in enumerate(child_sha1): - self.table.set_row_spacing(idx + 3, 0) - - align = gtk.Alignment(0.0, 0.0) - self.children_widgets.append(align) - self.table.attach(align, 3, 4, idx + 3, idx + 4, - gtk.EXPAND | gtk.FILL, gtk.FILL) - align.show() - - hbox = gtk.HBox(False, 0) - align.add(hbox) - hbox.show() - - label = gtk.Label(child_id) - label.set_selectable(True) - hbox.pack_start(label, expand=False, fill=True) - label.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.connect("clicked", self._go_clicked_cb, child_id) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.set_sensitive(True) - button.connect("clicked", self._show_clicked_cb, - child_id, commit.commit_sha1, self.encoding) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - def _destroy_cb(self, widget): - """Callback for when a window we manage is destroyed.""" - self.quit() - - - def quit(self): - """Stop the GTK+ main loop.""" - gtk.main_quit() - - def run(self, args): - self.set_branch(args) - self.window.connect("destroy", self._destroy_cb) - self.window.show() - gtk.main() - - def set_branch(self, args): - """Fill in different windows with info from the reposiroty""" - fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1)) - git_rev_list_cmd = fp.read() - fp.close() - fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd) - self.update_window(fp) - - def update_window(self, fp): - commit_lines = [] - - self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str) - - # used for cursor positioning - self.index = {} - - self.colours = {} - self.nodepos = {} - self.incomplete_line = {} - self.commits = [] - - index = 0 - last_colour = 0 - last_nodepos = -1 - out_line = [] - input_line = fp.readline() - while (input_line != ""): - # The commit header ends with '\0' - # This NULL is immediately followed by the sha1 of the - # next commit - if (input_line[0] != '\0'): - commit_lines.append(input_line) - input_line = fp.readline() - continue; - - commit = Commit(commit_lines) - if (commit != None ): - self.commits.append(commit) - - # Skip the '\0 - commit_lines = [] - commit_lines.append(input_line[1:]) - input_line = fp.readline() - - fp.close() - - for commit in self.commits: - (out_line, last_colour, last_nodepos) = self.draw_graph(commit, - index, out_line, - last_colour, - last_nodepos) - self.index[commit.commit_sha1] = index - index += 1 - - self.treeview.set_model(self.model) - self.treeview.show() - - def draw_graph(self, commit, index, out_line, last_colour, last_nodepos): - in_line=[] - - # | -> outline - # X - # |\ <- inline - - # Reset nodepostion - if (last_nodepos > 5): - last_nodepos = -1 - - # Add the incomplete lines of the last cell in this - try: - colour = self.colours[commit.commit_sha1] - except KeyError: - self.colours[commit.commit_sha1] = last_colour+1 - last_colour = self.colours[commit.commit_sha1] - colour = self.colours[commit.commit_sha1] - - try: - node_pos = self.nodepos[commit.commit_sha1] - except KeyError: - self.nodepos[commit.commit_sha1] = last_nodepos+1 - last_nodepos = self.nodepos[commit.commit_sha1] - node_pos = self.nodepos[commit.commit_sha1] - - #The first parent always continue on the same line - try: - # check we already have the value - tmp_node_pos = self.nodepos[commit.parent_sha1[0]] - except KeyError: - self.colours[commit.parent_sha1[0]] = colour - self.nodepos[commit.parent_sha1[0]] = node_pos - - for sha1 in self.incomplete_line.keys(): - if (sha1 != commit.commit_sha1): - self.draw_incomplete_line(sha1, node_pos, - out_line, in_line, index) - else: - del self.incomplete_line[sha1] - - - for parent_id in commit.parent_sha1: - try: - tmp_node_pos = self.nodepos[parent_id] - except KeyError: - self.colours[parent_id] = last_colour+1 - last_colour = self.colours[parent_id] - self.nodepos[parent_id] = last_nodepos+1 - last_nodepos = self.nodepos[parent_id] - - in_line.append((node_pos, self.nodepos[parent_id], - self.colours[parent_id])) - self.add_incomplete_line(parent_id) - - try: - branch_tag = self.bt_sha1[commit.commit_sha1] - except KeyError: - branch_tag = [ ] - - - node = (node_pos, colour, branch_tag) - - self.model.append([commit, node, out_line, in_line, - commit.message, commit.author, commit.date]) - - return (in_line, last_colour, last_nodepos) - - def add_incomplete_line(self, sha1): - try: - self.incomplete_line[sha1].append(self.nodepos[sha1]) - except KeyError: - self.incomplete_line[sha1] = [self.nodepos[sha1]] - - def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index): - for idx, pos in enumerate(self.incomplete_line[sha1]): - if(pos == node_pos): - #remove the straight line and add a slash - if ((pos, pos, self.colours[sha1]) in out_line): - out_line.remove((pos, pos, self.colours[sha1])) - out_line.append((pos, pos+0.5, self.colours[sha1])) - self.incomplete_line[sha1][idx] = pos = pos+0.5 - try: - next_commit = self.commits[index+1] - if (next_commit.commit_sha1 == sha1 and pos != int(pos)): - # join the line back to the node point - # This need to be done only if we modified it - in_line.append((pos, pos-0.5, self.colours[sha1])) - continue; - except IndexError: - pass - in_line.append((pos, pos, self.colours[sha1])) - - - def _go_clicked_cb(self, widget, revid): - """Callback for when the go button for a parent is clicked.""" - try: - self.treeview.set_cursor(self.index[revid]) - except KeyError: - dialog = gtk.MessageDialog(parent=None, flags=0, - type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE, - message_format=None) - dialog.set_markup("Revision %s not present in the list" % revid) - # revid == 0 is the parent of the first commit - if (revid != 0 ): - dialog.format_secondary_text("Try running gitview without any options") - dialog.run() - dialog.destroy() - - self.treeview.grab_focus() - - def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding): - """Callback for when the show button for a parent is clicked.""" - window = DiffWindow() - window.set_diff(commit_sha1, parent_sha1, encoding) - self.treeview.grab_focus() - -without_diff = 0 -if __name__ == "__main__": - - if (len(sys.argv) > 1 ): - if (sys.argv[1] == "--without-diff"): - without_diff = 1 - - view = GitView( without_diff != 1) - view.run(sys.argv[without_diff:]) diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt deleted file mode 100644 index 7b5f9002b96b3f..00000000000000 --- a/contrib/gitview/gitview.txt +++ /dev/null @@ -1,57 +0,0 @@ -gitview(1) -========== - -NAME ----- -gitview - A GTK based repository browser for git - -SYNOPSIS --------- -[verse] -'gitview' [options] [args] - -DESCRIPTION ---------- - -Dependencies: - -* Python 2.4 -* PyGTK 2.8 or later -* PyCairo 1.0 or later - -OPTIONS -------- ---without-diff:: - - If the user doesn't want to list the commit diffs in the main window. - This may speed up the repository browsing. - -:: - - All the valid option for linkgit:git-rev-list[1]. - -Key Bindings ------------- -F4:: - To maximize the window - -F5:: - To reread references. - -F11:: - Full screen - -F12:: - Leave full screen - -EXAMPLES --------- - -gitview v2.6.12.. include/scsi drivers/scsi:: - - Show as the changes since version v2.6.12 that changed any file in the - include/scsi or drivers/scsi subdirectories - -gitview --since=2.weeks.ago:: - - Show the changes during the last two weeks From 16615f0fbc445ef958f2453c926526d5e2280700 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 11:40:20 +0100 Subject: [PATCH 0138/1540] mingw: add a regression test for pushing to UNC paths On Windows, there are "UNC paths" to access network (AKA shared) folders, of the form \\server\sharename\directory. This provides a convenient way for Windows developers to share their Git repositories without having to have a dedicated server. Git for Windows v2.11.0 introduced a regression where pushing to said UNC paths no longer works, although fetching and cloning still does, as reported here: https://github.com/git-for-windows/git/issues/979 This regression was fixed in 7814fbe3f1 (normalize_path_copy(): fix pushing to //server/share/dir on Windows, 2016-12-14). Let's make sure that it does not regress again, by introducing a test that uses so-called "administrative shares": disk volumes are automatically shared under certain circumstances, e.g. the C: drive is shared as \\localhost\c$. The test needs to be skipped if the current directory is inaccessible via said administrative share, of course. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t5580-clone-push-unc.sh | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100755 t/t5580-clone-push-unc.sh diff --git a/t/t5580-clone-push-unc.sh b/t/t5580-clone-push-unc.sh new file mode 100755 index 00000000000000..b195f71ea98423 --- /dev/null +++ b/t/t5580-clone-push-unc.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='various UNC path tests (Windows-only)' +. ./test-lib.sh + +if ! test_have_prereq MINGW; then + skip_all='skipping UNC path tests, requires Windows' + test_done +fi + +UNCPATH="$(pwd)" +case "$UNCPATH" in +[A-Z]:*) + # Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git + # (we use forward slashes here because MSYS2 and Git accept them, and + # they are easier on the eyes) + UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}" + test -d "$UNCPATH" || { + skip_all='could not access administrative share; skipping' + test_done + } + ;; +*) + skip_all='skipping UNC path tests, cannot determine current path as UNC' + test_done + ;; +esac + +test_expect_success setup ' + test_commit initial +' + +test_expect_success clone ' + git clone "file://$UNCPATH" clone +' + +test_expect_success push ' + ( + cd clone && + git checkout -b to-push && + test_commit to-push && + git push origin HEAD + ) && + rev="$(git -C clone rev-parse --verify refs/heads/to-push)" && + test "$rev" = "$(git rev-parse --verify refs/heads/to-push)" +' + +test_done From 9574901c02d5f216efe20a9995c2f2056360f9f1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 17:04:05 +0100 Subject: [PATCH 0139/1540] giteveryday: unbreak rendering with AsciiDoctor The "giteveryday" document has a callout list that contains a code block. This is not a problem for AsciiDoc, but AsciiDoctor sadly was explicitly designed *not* to render this correctly [*1*]. The symptom is an unhelpful line 322: callout list item index: expected 1 got 12 line 325: no callouts refer to list item 1 line 325: callout list item index: expected 2 got 13 line 327: no callouts refer to list item 2 In Git for Windows, we rely on the speed improvement of AsciiDoctor (on this developer's machine, `make -j15 html` takes roughly 30 seconds with AsciiDoctor, 70 seconds with AsciiDoc), therefore we need a way to render this correctly. The easiest way out is to simplify the callout list, as suggested by AsciiDoctor's author, even while one may very well disagree with him that a code block hath no place in a callout list. *1*: https://github.com/asciidoctor/asciidoctor/issues/1478 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/giteveryday.txt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt index 35473ad02fb1dc..10c8ff93c0b9f5 100644 --- a/Documentation/giteveryday.txt +++ b/Documentation/giteveryday.txt @@ -307,9 +307,16 @@ master or exposed as a part of a stable branch. <9> backport a critical fix. <10> create a signed tag. <11> make sure master was not accidentally rewound beyond that -already pushed out. `ko` shorthand points at the Git maintainer's +already pushed out. +<12> In the output from `git show-branch`, `master` should have +everything `ko/master` has, and `next` should have +everything `ko/next` has, etc. +<13> push out the bleeding edge, together with new tags that point +into the pushed history. + +In this example, the `ko` shorthand points at the Git maintainer's repository at kernel.org, and looks like this: -+ + ------------ (in .git/config) [remote "ko"] @@ -320,12 +327,6 @@ repository at kernel.org, and looks like this: push = +refs/heads/pu push = refs/heads/maint ------------ -+ -<12> In the output from `git show-branch`, `master` should have -everything `ko/master` has, and `next` should have -everything `ko/next` has, etc. -<13> push out the bleeding edge, together with new tags that point -into the pushed history. Repository Administration[[ADMINISTRATION]] From 965cba2e7ee9b272a35d66a11f0a7bf544aa727a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 2 Jan 2017 17:25:09 -0500 Subject: [PATCH 0140/1540] archive-zip: load userdiff config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 4aff646d17 (archive-zip: mark text files in archives, 2015-03-05), the zip archiver will look at the userdiff driver to decide whether a file is text or binary. This usually doesn't need to look any further than the attributes themselves (e.g., "-diff", etc). But if the user defines a custom driver like "diff=foo", we need to look at "diff.foo.binary" in the config. Prior to this patch, we didn't actually load it. Signed-off-by: Jeff King Acked-by: René Scharfe Signed-off-by: Junio C Hamano --- archive-zip.c | 7 +++++++ t/t5003-archive-zip.sh | 22 ++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/archive-zip.c b/archive-zip.c index 9db47357b02d4c..b429a8d974a02b 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -554,11 +554,18 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time) *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; } +static int archive_zip_config(const char *var, const char *value, void *data) +{ + return userdiff_config(var, value); +} + static int write_zip_archive(const struct archiver *ar, struct archiver_args *args) { int err; + git_config(archive_zip_config, NULL); + dos_time(&args->time, &zip_date, &zip_time); zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 14744b2a4b1e64..55c78709978ff7 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -64,6 +64,12 @@ check_zip() { test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf && test_cmp_bin $original/nodiff.lf $extracted/nodiff.lf " + + test_expect_success UNZIP " validate that custom diff is unchanged " " + test_cmp_bin $original/custom.cr $extracted/custom.cr && + test_cmp_bin $original/custom.crlf $extracted/custom.crlf && + test_cmp_bin $original/custom.lf $extracted/custom.lf + " } test_expect_success \ @@ -78,6 +84,9 @@ test_expect_success \ printf "text\r" >a/nodiff.cr && printf "text\r\n" >a/nodiff.crlf && printf "text\n" >a/nodiff.lf && + printf "text\r" >a/custom.cr && + printf "text\r\n" >a/custom.crlf && + printf "text\n" >a/custom.lf && printf "\0\r" >a/binary.cr && printf "\0\r\n" >a/binary.crlf && printf "\0\n" >a/binary.lf && @@ -112,15 +121,20 @@ test_expect_success 'add files to repository' ' test_expect_success 'setup export-subst and diff attributes' ' echo "a/nodiff.* -diff" >>.git/info/attributes && echo "a/diff.* diff" >>.git/info/attributes && + echo "a/custom.* diff=custom" >>.git/info/attributes && + git config diff.custom.binary true && echo "substfile?" export-subst >>.git/info/attributes && git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ >a/substfile1 ' -test_expect_success \ - 'create bare clone' \ - 'git clone --bare . bare.git && - cp .git/info/attributes bare.git/info/attributes' +test_expect_success 'create bare clone' ' + git clone --bare . bare.git && + cp .git/info/attributes bare.git/info/attributes && + # Recreate our changes to .git/config rather than just copying it, as + # we do not want to clobber core.bare or other settings. + git -C bare.git config diff.custom.binary true +' test_expect_success \ 'remove ignored file' \ From b10731f43dc21fa47c275052e7c074c686335cd3 Mon Sep 17 00:00:00 2001 From: Kyle Meyer Date: Fri, 6 Jan 2017 20:12:15 -0500 Subject: [PATCH 0141/1540] branch_get_push: do not segfault when HEAD is detached Move the detached HEAD check from branch_get_push_1() to branch_get_push() to avoid setting branch->push_tracking_ref when branch is NULL. Signed-off-by: Kyle Meyer Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- remote.c | 6 +++--- t/t1514-rev-parse-push.sh | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/remote.c b/remote.c index a326e4e2516e21..d1a7a4032b6367 100644 --- a/remote.c +++ b/remote.c @@ -1717,9 +1717,6 @@ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err) { struct remote *remote; - if (!branch) - return error_buf(err, _("HEAD does not point to a branch")); - remote = remote_get(pushremote_for_branch(branch, NULL)); if (!remote) return error_buf(err, @@ -1779,6 +1776,9 @@ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err) const char *branch_get_push(struct branch *branch, struct strbuf *err) { + if (!branch) + return error_buf(err, _("HEAD does not point to a branch")); + if (!branch->push_tracking_ref) branch->push_tracking_ref = branch_get_push_1(branch, err); return branch->push_tracking_ref; diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh index 7214f5b33fa41f..623a32aa6e323d 100755 --- a/t/t1514-rev-parse-push.sh +++ b/t/t1514-rev-parse-push.sh @@ -60,4 +60,10 @@ test_expect_success '@{push} with push refspecs' ' resolve topic@{push} refs/remotes/origin/magic/topic ' +test_expect_success 'resolving @{push} fails with a detached HEAD' ' + git checkout HEAD^0 && + test_when_finished "git checkout -" && + test_must_fail git rev-parse @{push} +' + test_done From 356b8ecff101e3f763619d74f344ede3204c7991 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 7 Jan 2017 03:23:19 -0500 Subject: [PATCH 0142/1540] rebase--interactive: count squash commits above 10 correctly We generate the squash commit message incrementally running a sed script once for each commit. It parses "This is a combination of commits" from the first line of the existing message, adds one to , and uses the result as the number of our current message. Since f2d17068fd (i18n: rebase-interactive: mark comments of squash for translation, 2016-06-17), the first line may be localized, and sed uses a pretty liberal regex, looking for: /^#.*([0-9][0-9]*)/ The "[0-9][0-9]*" tries to match double digits, but it doesn't quite work. The first ".*" is greedy, so if you have: This is a combination of 10 commits. it will eat up "This is a combination of 1", leaving "0" to match the first "[0-9]" digit, and then skipping the optional match of "[0-9]*". As a result, the count resets every 10 commits, and a 15-commit squash would end up as: # This is a combination of 5 commits. # This is the 1st commit message: ... # This is the commit message #2: ... and so on .. # This is the commit message #10: ... # This is the commit message #1: ... # This is the commit message #2: ... etc, up to 5 ... We can fix this by making the ".*" less greedy. Instead of depending on ".*?" working portably, we can just limit the match to non-digit characters, which accomplishes the same thing. Reported-by: Brandon Tolsch Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index ca994c5c545cb0..4bca73c94cc968 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -416,7 +416,7 @@ update_squash_messages () { if test -f "$squash_msg"; then mv "$squash_msg" "$squash_msg".bak || exit count=$(($(sed -n \ - -e "1s/^$comment_char.*\([0-9][0-9]*\).*/\1/p" \ + -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \ -e "q" < "$squash_msg".bak)+1)) { printf '%s\n' "$comment_char $(eval_ngettext \ From 91229834c293302c4456e732ddef7ace0df6e471 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 5 Jan 2017 23:17:40 -0500 Subject: [PATCH 0143/1540] blame: fix alignment with --abbrev=40 The blame command internally adds 1 to any requested sha1 abbreviation length, and then subtracts it when outputting a boundary commit. This lets regular and boundary sha1s line up visually, but it misses one corner case. When the requested length is 40, we bump the value to 41. But since we only have 40 characters, that's all we can show (fortunately the truncation is done by a printf precision field, so it never tries to read past the end of the buffer). So a normal sha1 shows 40 hex characters, and a boundary sha1 shows "^" plus 40 hex characters. The result is misaligned. The "-l" option to show long sha1s gets around this by skipping the "abbrev" variable entirely and just always using GIT_SHA1_HEXSZ. This avoids the "+1" issue, but it does mean that boundary commits only have 39 characters printed. This is somewhat odd, but it does look good visually: the results are aligned and left-justified. The alternative would be to allocate an extra column that would contain either an extra space or the "^" boundary marker. As this is by definition the human-readable view, it's probably not that big a deal either way (and of course --porcelain, etc, correctly produce correct 40-hex sha1s). But for consistency, this patch teaches --abbrev=40 to produce the same output as "-l" (always left-aligned, with 40-hex for normal sha1s, and "^" plus 39-hex for boundaries). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/blame.c | 2 +- t/t8002-blame.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/builtin/blame.c b/builtin/blame.c index f618392e55f709..cbb7dc2ad9e9ce 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2606,7 +2606,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } else if (show_progress < 0) show_progress = isatty(2); - if (0 < abbrev) + if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) /* one more abbrev length is needed for the boundary commit */ abbrev++; diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index ab79de95441f4f..c6347ad8fde4fe 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -86,4 +86,32 @@ test_expect_success 'blame with showEmail config true' ' test_cmp expected_n result ' +test_expect_success 'set up abbrev tests' ' + test_commit abbrev && + sha1=$(git rev-parse --verify HEAD) && + check_abbrev () { + expect=$1; shift + echo $sha1 | cut -c 1-$expect >expect && + git blame "$@" abbrev.t >actual && + perl -lne "/[0-9a-f]+/ and print \$&" actual.sha && + test_cmp expect actual.sha + } +' + +test_expect_success 'blame --abbrev= works' ' + # non-boundary commits get +1 for alignment + check_abbrev 31 --abbrev=30 HEAD && + check_abbrev 30 --abbrev=30 ^HEAD +' + +test_expect_success 'blame -l aligns regular and boundary commits' ' + check_abbrev 40 -l HEAD && + check_abbrev 39 -l ^HEAD +' + +test_expect_success 'blame --abbrev=40 behaves like -l' ' + check_abbrev 40 --abbrev=40 HEAD && + check_abbrev 39 --abbrev=40 ^HEAD +' + test_done From ed58d8088b570e7629bfc94b87e433f05229ef3c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 5 Jan 2017 23:18:08 -0500 Subject: [PATCH 0144/1540] blame: handle --no-abbrev You can already ask blame for full sha1s with "-l" or with "--abbrev=40". But for consistency with other parts of Git, we should support "--no-abbrev". Worse, blame already accepts --no-abbrev, but it's totally broken. When we see --no-abbrev, the abbrev variable is set to 0, which is then used as a printf precision. For regular sha1s, that means we print nothing at all (which is very wrong). For boundary commits we decrement it to "-1", which printf interprets as "no limit" (which is almost correct, except it misses the 39-length magic explained in the previous commit). Let's detect --no-abbrev and behave as if --abbrev=40 was given. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/blame.c | 2 ++ t/t8002-blame.sh | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/builtin/blame.c b/builtin/blame.c index cbb7dc2ad9e9ce..1fccbe6bff1995 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2609,6 +2609,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) /* one more abbrev length is needed for the boundary commit */ abbrev++; + else if (!abbrev) + abbrev = GIT_SHA1_HEXSZ; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index c6347ad8fde4fe..380e1c1054de5d 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -114,4 +114,8 @@ test_expect_success 'blame --abbrev=40 behaves like -l' ' check_abbrev 39 --abbrev=40 ^HEAD ' +test_expect_success '--no-abbrev works like --abbrev=40' ' + check_abbrev 40 --no-abbrev +' + test_done From 4e768329847c8226a230406041fe249adea245cf Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 5 Jan 2017 23:20:51 -0500 Subject: [PATCH 0145/1540] blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/blame.c | 23 ++++--- t/t8011-blame-split-file.sh | 117 ++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 9 deletions(-) create mode 100755 t/t8011-blame-split-file.sh diff --git a/builtin/blame.c b/builtin/blame.c index 1fccbe6bff1995..d4f3ce96a563f1 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1700,13 +1700,23 @@ static void get_commit_info(struct commit *commit, } /* + * Write out any suspect information which depends on the path. This must be + * handled separately from emit_one_suspect_detail(), because a given commit + * may have changes in multiple paths. So this needs to appear each time + * we mention a new group. + * * To allow LF and other nonportable characters in pathnames, * they are c-style quoted as needed. */ -static void write_filename_info(const char *path) +static void write_filename_info(struct origin *suspect) { + if (suspect->previous) { + struct origin *prev = suspect->previous; + printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); + write_name_quoted(prev->path, stdout, '\n'); + } printf("filename "); - write_name_quoted(path, stdout, '\n'); + write_name_quoted(suspect->path, stdout, '\n'); } /* @@ -1735,11 +1745,6 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); - if (suspect->previous) { - struct origin *prev = suspect->previous; - printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); - write_name_quoted(prev->path, stdout, '\n'); - } commit_info_destroy(&ci); @@ -1760,7 +1765,7 @@ static void found_guilty_entry(struct blame_entry *ent, oid_to_hex(&suspect->commit->object.oid), ent->s_lno + 1, ent->lno + 1, ent->num_lines); emit_one_suspect_detail(suspect, 0); - write_filename_info(suspect->path); + write_filename_info(suspect); maybe_flush_or_die(stdout, "stdout"); } pi->blamed_lines += ent->num_lines; @@ -1884,7 +1889,7 @@ static void emit_porcelain_details(struct origin *suspect, int repeat) { if (emit_one_suspect_detail(suspect, repeat) || (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) - write_filename_info(suspect->path); + write_filename_info(suspect); } static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, diff --git a/t/t8011-blame-split-file.sh b/t/t8011-blame-split-file.sh new file mode 100755 index 00000000000000..831125047b93a4 --- /dev/null +++ b/t/t8011-blame-split-file.sh @@ -0,0 +1,117 @@ +#!/bin/sh + +test_description=' +The general idea is that we have a single file whose lines come from +multiple other files, and those individual files were modified in the same +commits. That means that we will see the same commit in multiple contexts, +and each one should be attributed to the correct file. + +Note that we need to use "blame -C" to find the commit for all lines. We will +not bother testing that the non-C case fails to find it. That is how blame +behaves now, but it is not a property we want to make sure is retained. +' +. ./test-lib.sh + +# help avoid typing and reading long strings of similar lines +# in the tests below +generate_expect () { + while read nr data + do + i=0 + while test $i -lt $nr + do + echo $data + i=$((i + 1)) + done + done +} + +test_expect_success 'setup split file case' ' + # use lines long enough to trigger content detection + test_seq 1000 1010 >one && + test_seq 2000 2010 >two && + git add one two && + test_commit base && + + sed "6s/^/modified /" one.tmp && + mv one.tmp one && + sed "6s/^/modified /" two.tmp && + mv two.tmp two && + git add -u && + test_commit modified && + + cat one two >combined && + git add combined && + git rm one two && + test_commit combined +' + +test_expect_success 'setup simulated porcelain' ' + # This just reads porcelain-ish output and tries + # to output the value of a given field for each line (either by + # reading the field that accompanies this line, or referencing + # the information found last time the commit was mentioned). + cat >read-porcelain.pl <<-\EOF + my $field = shift; + while (<>) { + if (/^[0-9a-f]{40} /) { + flush(); + $hash = $&; + } elsif (/^$field (.*)/) { + $cache{$hash} = $1; + } + } + flush(); + + sub flush { + return unless defined $hash; + if (defined $cache{$hash}) { + print "$cache{$hash}\n"; + } else { + print "NONE\n"; + } + } + EOF +' + +for output in porcelain line-porcelain +do + test_expect_success "generate --$output output" ' + git blame --root -C --$output combined >output + ' + + test_expect_success "$output output finds correct commits" ' + generate_expect >expect <<-\EOF && + 5 base + 1 modified + 10 base + 1 modified + 5 base + EOF + perl read-porcelain.pl summary actual && + test_cmp expect actual + ' + + test_expect_success "$output output shows correct filenames" ' + generate_expect >expect <<-\EOF && + 11 one + 11 two + EOF + perl read-porcelain.pl filename actual && + test_cmp expect actual + ' + + test_expect_success "$output output shows correct previous pointer" ' + generate_expect >expect <<-EOF && + 5 NONE + 1 $(git rev-parse modified^) one + 10 NONE + 1 $(git rev-parse modified^) two + 5 NONE + EOF + perl read-porcelain.pl previous actual && + test_cmp expect actual + ' +done + +test_done From c9bb5d101ca657fa466afa8c4368c43ea7b7aca8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 17:22:33 +0100 Subject: [PATCH 0146/1540] git_exec_path: avoid Coverity warning about unfree()d result Technically, it is correct that git_exec_path() returns a possibly malloc()ed string returned from system_path(), and it is sometimes not allocated. Cache the result in a static variable and make sure that we call system_path() only once, which plugs a potential leak. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- exec_cmd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exec_cmd.c b/exec_cmd.c index 9d5703a157fe8e..eae56fefba9953 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -69,6 +69,7 @@ void git_set_argv_exec_path(const char *exec_path) const char *git_exec_path(void) { const char *env; + static char *system_exec_path; if (argv_exec_path) return argv_exec_path; @@ -78,7 +79,9 @@ const char *git_exec_path(void) return env; } - return system_path(GIT_EXEC_PATH); + if (!system_exec_path) + system_exec_path = system_path(GIT_EXEC_PATH); + return system_exec_path; } static void add_path(struct strbuf *out, const char *path) From 2ec87741b2d2935542b13d407fbeedbe0a7af094 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:03:56 -0800 Subject: [PATCH 0147/1540] mv: remove use of deprecated 'get_pathspec()' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the 'internal_copy_pathspec()' function to 'prefix_path()' instead of using the deprecated 'get_pathspec()' interface. Also, rename 'internal_copy_pathspec()' to 'internal_prefix_pathspec()' to be more descriptive of what the funciton is actually doing. In addition to this, fix a memory leak caused by only duplicating some of the pathspec elements. Instead always duplicate all of the the pathspec elements as an intermediate step (with modificationed based on the passed in flags). This way the intermediate strings can then be freed after getting the result from 'prefix_path()'. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/mv.c | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index 2f43877bc9a17c..4e86dc5234ee62 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Johannes Schindelin */ #include "builtin.h" +#include "pathspec.h" #include "lockfile.h" #include "dir.h" #include "cache-tree.h" @@ -19,31 +20,42 @@ static const char * const builtin_mv_usage[] = { #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 -static const char **internal_copy_pathspec(const char *prefix, - const char **pathspec, - int count, unsigned flags) +static const char **internal_prefix_pathspec(const char *prefix, + const char **pathspec, + int count, unsigned flags) { int i; const char **result; + int prefixlen = prefix ? strlen(prefix) : 0; ALLOC_ARRAY(result, count + 1); - COPY_ARRAY(result, pathspec, count); - result[count] = NULL; + + /* Create an intermediate copy of the pathspec based on the flags */ for (i = 0; i < count; i++) { - int length = strlen(result[i]); + int length = strlen(pathspec[i]); int to_copy = length; + char *it; while (!(flags & KEEP_TRAILING_SLASH) && - to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) + to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) to_copy--; - if (to_copy != length || flags & DUP_BASENAME) { - char *it = xmemdupz(result[i], to_copy); - if (flags & DUP_BASENAME) { - result[i] = xstrdup(basename(it)); - free(it); - } else - result[i] = it; + + it = xmemdupz(pathspec[i], to_copy); + if (flags & DUP_BASENAME) { + result[i] = xstrdup(basename(it)); + free(it); + } else { + result[i] = it; } } - return get_pathspec(prefix, result); + result[count] = NULL; + + /* Prefix the pathspec and free the old intermediate strings */ + for (i = 0; i < count; i++) { + const char *match = prefix_path(prefix, prefixlen, result[i]); + free((char *) result[i]); + result[i] = match; + } + + return result; } static const char *add_slash(const char *path) @@ -130,7 +142,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - source = internal_copy_pathspec(prefix, argv, argc, 0); + source = internal_prefix_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); /* * Keep trailing slash, needed to let @@ -140,16 +152,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = KEEP_TRAILING_SLASH; if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; - dest_path = internal_copy_pathspec(prefix, argv + argc, 1, flags); + dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); submodule_gitfile = xcalloc(argc, sizeof(char *)); if (dest_path[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); - destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); } else { if (argc != 1) die(_("destination '%s' is not a directory"), dest_path[0]); From e1b8c7bdc0ceba64e4d9fa91b951af3d1de18870 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:03:57 -0800 Subject: [PATCH 0148/1540] dir: remove struct path_simplify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teach simplify_away() and exclude_matches_pathspec() to handle struct pathspec directly, eliminating the need for the struct path_simplify. Also renamed the len parameter to pathlen in exclude_matches_pathspec() to match the parameter names used in simplify_away(). Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 179 +++++++++++++++++++++++++--------------------------------- 1 file changed, 76 insertions(+), 103 deletions(-) diff --git a/dir.c b/dir.c index bfa8c8a9a51297..9ae454ddec3764 100644 --- a/dir.c +++ b/dir.c @@ -16,11 +16,6 @@ #include "varint.h" #include "ewah/ewok.h" -struct path_simplify { - int len; - const char *path; -}; - /* * Tells read_directory_recursive how a file or directory should be treated. * Values are ordered by significance, e.g. if a directory contains both @@ -50,7 +45,7 @@ struct cached_dir { static enum path_treatment read_directory_recursive(struct dir_struct *dir, const char *path, int len, struct untracked_cache_dir *untracked, - int check_only, const struct path_simplify *simplify); + int check_only, const struct pathspec *pathspec); static int get_dtype(struct dirent *de, const char *path, int len); int fspathcmp(const char *a, const char *b) @@ -1312,7 +1307,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) static enum path_treatment treat_directory(struct dir_struct *dir, struct untracked_cache_dir *untracked, const char *dirname, int len, int baselen, int exclude, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { /* The "len-1" is to strip the final '/' */ switch (directory_exists_in_index(dirname, len-1)) { @@ -1341,7 +1336,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, untracked = lookup_untracked(dir->untracked, untracked, dirname + baselen, len - baselen); return read_directory_recursive(dir, dirname, len, - untracked, 1, simplify); + untracked, 1, pathspec); } /* @@ -1349,24 +1344,33 @@ static enum path_treatment treat_directory(struct dir_struct *dir, * reading - if the path cannot possibly be in the pathspec, * return true, and we'll skip it early. */ -static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify) +static int simplify_away(const char *path, int pathlen, + const struct pathspec *pathspec) { - if (simplify) { - for (;;) { - const char *match = simplify->path; - int len = simplify->len; + int i; - if (!match) - break; - if (len > pathlen) - len = pathlen; - if (!memcmp(path, match, len)) - return 0; - simplify++; - } - return 1; + if (!pathspec || !pathspec->nr) + return 0; + + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE | + PATHSPEC_EXCLUDE); + + for (i = 0; i < pathspec->nr; i++) { + const struct pathspec_item *item = &pathspec->items[i]; + int len = item->nowildcard_len; + + if (len > pathlen) + len = pathlen; + if (!ps_strncmp(item, item->match, path, len)) + return 0; } - return 0; + + return 1; } /* @@ -1380,19 +1384,33 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli * 2. the path is a directory prefix of some element in the * pathspec */ -static int exclude_matches_pathspec(const char *path, int len, - const struct path_simplify *simplify) -{ - if (simplify) { - for (; simplify->path; simplify++) { - if (len == simplify->len - && !memcmp(path, simplify->path, len)) - return 1; - if (len < simplify->len - && simplify->path[len] == '/' - && !memcmp(path, simplify->path, len)) - return 1; - } +static int exclude_matches_pathspec(const char *path, int pathlen, + const struct pathspec *pathspec) +{ + int i; + + if (!pathspec || !pathspec->nr) + return 0; + + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE | + PATHSPEC_EXCLUDE); + + for (i = 0; i < pathspec->nr; i++) { + const struct pathspec_item *item = &pathspec->items[i]; + int len = item->nowildcard_len; + + if (len == pathlen && + !ps_strncmp(item, item->match, path, pathlen)) + return 1; + if (len > pathlen && + item->match[pathlen] == '/' && + !ps_strncmp(item, item->match, path, pathlen)) + return 1; } return 0; } @@ -1460,7 +1478,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct strbuf *path, int baselen, - const struct path_simplify *simplify, + const struct pathspec *pathspec, int dtype, struct dirent *de) { int exclude; @@ -1512,7 +1530,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, case DT_DIR: strbuf_addch(path, '/'); return treat_directory(dir, untracked, path->buf, path->len, - baselen, exclude, simplify); + baselen, exclude, pathspec); case DT_REG: case DT_LNK: return exclude ? path_excluded : path_untracked; @@ -1524,7 +1542,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, struct cached_dir *cdir, struct strbuf *path, int baselen, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { strbuf_setlen(path, baselen); if (!cdir->ucd) { @@ -1541,7 +1559,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, * with check_only set. */ return read_directory_recursive(dir, path->buf, path->len, - cdir->ucd, 1, simplify); + cdir->ucd, 1, pathspec); /* * We get path_recurse in the first run when * directory_exists_in_index() returns index_nonexistent. We @@ -1556,23 +1574,23 @@ static enum path_treatment treat_path(struct dir_struct *dir, struct cached_dir *cdir, struct strbuf *path, int baselen, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { int dtype; struct dirent *de = cdir->de; if (!de) return treat_path_fast(dir, untracked, cdir, path, - baselen, simplify); + baselen, pathspec); if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) return path_none; strbuf_setlen(path, baselen); strbuf_addstr(path, de->d_name); - if (simplify_away(path->buf, path->len, simplify)) + if (simplify_away(path->buf, path->len, pathspec)) return path_none; dtype = DTYPE(de); - return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de); + return treat_one_path(dir, untracked, path, baselen, pathspec, dtype, de); } static void add_untracked(struct untracked_cache_dir *dir, const char *name) @@ -1703,7 +1721,7 @@ static void close_cached_dir(struct cached_dir *cdir) static enum path_treatment read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, struct untracked_cache_dir *untracked, int check_only, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { struct cached_dir cdir; enum path_treatment state, subdir_state, dir_state = path_none; @@ -1719,7 +1737,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, while (!read_cached_dir(&cdir)) { /* check how the file or directory should be treated */ - state = treat_path(dir, untracked, &cdir, &path, baselen, simplify); + state = treat_path(dir, untracked, &cdir, &path, + baselen, pathspec); if (state > dir_state) dir_state = state; @@ -1731,8 +1750,9 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, path.buf + baselen, path.len - baselen); subdir_state = - read_directory_recursive(dir, path.buf, path.len, - ud, check_only, simplify); + read_directory_recursive(dir, path.buf, + path.len, ud, + check_only, pathspec); if (subdir_state > dir_state) dir_state = subdir_state; } @@ -1756,7 +1776,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, else if ((dir->flags & DIR_SHOW_IGNORED_TOO) || ((dir->flags & DIR_COLLECT_IGNORED) && exclude_matches_pathspec(path.buf, path.len, - simplify))) + pathspec))) dir_add_ignored(dir, path.buf, path.len); break; @@ -1787,36 +1807,9 @@ static int cmp_name(const void *p1, const void *p2) return name_compare(e1->name, e1->len, e2->name, e2->len); } -static struct path_simplify *create_simplify(const char **pathspec) -{ - int nr, alloc = 0; - struct path_simplify *simplify = NULL; - - if (!pathspec) - return NULL; - - for (nr = 0 ; ; nr++) { - const char *match; - ALLOC_GROW(simplify, nr + 1, alloc); - match = *pathspec++; - if (!match) - break; - simplify[nr].path = match; - simplify[nr].len = simple_length(match); - } - simplify[nr].path = NULL; - simplify[nr].len = 0; - return simplify; -} - -static void free_simplify(struct path_simplify *simplify) -{ - free(simplify); -} - static int treat_leading_path(struct dir_struct *dir, const char *path, int len, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { struct strbuf sb = STRBUF_INIT; int baselen, rc = 0; @@ -1840,9 +1833,9 @@ static int treat_leading_path(struct dir_struct *dir, strbuf_add(&sb, path, baselen); if (!is_directory(sb.buf)) break; - if (simplify_away(sb.buf, sb.len, simplify)) + if (simplify_away(sb.buf, sb.len, pathspec)) break; - if (treat_one_path(dir, NULL, &sb, baselen, simplify, + if (treat_one_path(dir, NULL, &sb, baselen, pathspec, DT_DIR, NULL) == path_none) break; /* do not recurse into it */ if (len <= baselen) { @@ -2010,33 +2003,14 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return root; } -int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec) +int read_directory(struct dir_struct *dir, const char *path, + int len, const struct pathspec *pathspec) { - struct path_simplify *simplify; struct untracked_cache_dir *untracked; - /* - * Check out create_simplify() - */ - if (pathspec) - GUARD_PATHSPEC(pathspec, - PATHSPEC_FROMTOP | - PATHSPEC_MAXDEPTH | - PATHSPEC_LITERAL | - PATHSPEC_GLOB | - PATHSPEC_ICASE | - PATHSPEC_EXCLUDE); - if (has_symlink_leading_path(path, len)) return dir->nr; - /* - * exclude patterns are treated like positive ones in - * create_simplify. Usually exclude patterns should be a - * subset of positive ones, which has no impacts on - * create_simplify(). - */ - simplify = create_simplify(pathspec ? pathspec->_raw : NULL); untracked = validate_untracked_cache(dir, len, pathspec); if (!untracked) /* @@ -2044,9 +2018,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru * e.g. prep_exclude() */ dir->untracked = NULL; - if (!len || treat_leading_path(dir, path, len, simplify)) - read_directory_recursive(dir, path, len, untracked, 0, simplify); - free_simplify(simplify); + if (!len || treat_leading_path(dir, path, len, pathspec)) + read_directory_recursive(dir, path, len, untracked, 0, pathspec); QSORT(dir->entries, dir->nr, cmp_name); QSORT(dir->ignored, dir->ignored_nr, cmp_name); if (dir->untracked) { From 966de3028b190993329d2ed3af4d3d50059f6483 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:03:58 -0800 Subject: [PATCH 0149/1540] dir: convert fill_directory to use the pathspec struct interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert 'fill_directory()' to use the pathspec struct interface from using the '_raw' entry in the pathspec struct. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dir.c b/dir.c index 9ae454ddec3764..bc5ff72167a8e1 100644 --- a/dir.c +++ b/dir.c @@ -174,17 +174,21 @@ char *common_prefix(const struct pathspec *pathspec) int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) { - size_t len; + char *prefix; + size_t prefix_len; /* * Calculate common prefix for the pathspec, and * use that to optimize the directory walk */ - len = common_prefix_len(pathspec); + prefix = common_prefix(pathspec); + prefix_len = prefix ? strlen(prefix) : 0; /* Read the directory and prune it */ - read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec); - return len; + read_directory(dir, prefix, prefix_len, pathspec); + + free(prefix); + return prefix_len; } int within_depth(const char *name, int namelen, From e1e24edc1a86ef1f7639b3bbebc82c045d3c27b6 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:03:59 -0800 Subject: [PATCH 0150/1540] ls-tree: convert show_recursive to use the pathspec struct interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert 'show_recursive()' to use the pathspec struct interface from using the '_raw' entry in the pathspec struct. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 0e30d862303b67..d7ebeb4ce6b1f1 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -31,21 +31,18 @@ static const char * const ls_tree_usage[] = { static int show_recursive(const char *base, int baselen, const char *pathname) { - const char **s; + int i; if (ls_options & LS_RECURSIVE) return 1; - s = pathspec._raw; - if (!s) + if (!pathspec.nr) return 0; - for (;;) { - const char *spec = *s++; + for (i = 0; i < pathspec.nr; i++) { + const char *spec = pathspec.items[i].match; int len, speclen; - if (!spec) - return 0; if (strncmp(base, spec, baselen)) continue; len = strlen(pathname); @@ -59,6 +56,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) continue; return 1; } + return 0; } static int show_tree(const unsigned char *sha1, struct strbuf *base, @@ -175,8 +173,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * cannot be lifted until it is converted to use * match_pathspec() or tree_entry_interesting() */ - parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE, + parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & + ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), PATHSPEC_PREFER_CWD, prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) From 34305f7753f9f044cb280e6d58658cb31b140693 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:00 -0800 Subject: [PATCH 0151/1540] pathspec: remove the deprecated get_pathspec function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that all callers of the old 'get_pathspec' interface have been migrated to use the new pathspec struct interface it can be removed from the codebase. Since there are no more users of the '_raw' field in the pathspec struct it can also be removed. This patch also removes the old functionality of modifying the const char **argv array that was passed into parse_pathspec. Instead the constructed 'match' string (which is a pathspec element with the prefix prepended) is only stored in its corresponding pathspec_item entry. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/technical/api-setup.txt | 2 -- cache.h | 1 - pathspec.c | 42 ++------------------------- pathspec.h | 1 - 4 files changed, 3 insertions(+), 43 deletions(-) diff --git a/Documentation/technical/api-setup.txt b/Documentation/technical/api-setup.txt index 540e45568990f8..eb1fa9853ef6fd 100644 --- a/Documentation/technical/api-setup.txt +++ b/Documentation/technical/api-setup.txt @@ -27,8 +27,6 @@ parse_pathspec(). This function takes several arguments: - prefix and args come from cmd_* functions -get_pathspec() is obsolete and should never be used in new code. - parse_pathspec() helps catch unsupported features and reject them politely. At a lower level, different pathspec-related functions may not support the same set of features. Such pathspec-sensitive diff --git a/cache.h b/cache.h index a50a61a19787de..0f80e01bde5751 100644 --- a/cache.h +++ b/cache.h @@ -514,7 +514,6 @@ extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" -extern const char **get_pathspec(const char *prefix, const char **pathspec); extern void setup_work_tree(void); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); diff --git a/pathspec.c b/pathspec.c index 22ca74a126e799..1f918cbaec2e7b 100644 --- a/pathspec.c +++ b/pathspec.c @@ -103,7 +103,7 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen, */ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned *p_short_magic, - const char **raw, unsigned flags, + unsigned flags, const char *prefix, int prefixlen, const char *elt) { @@ -240,7 +240,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, if (!match) die(_("%s: '%s' is outside repository"), elt, copyfrom); } - *raw = item->match = match; + item->match = match; /* * Prefix the pathspec (keep all magic) and assign to * original. Useful for passing to another command. @@ -381,8 +381,6 @@ void parse_pathspec(struct pathspec *pathspec, /* No arguments with prefix -> prefix pathspec */ if (!entry) { - static const char *raw[2]; - if (flags & PATHSPEC_PREFER_FULL) return; @@ -394,10 +392,7 @@ void parse_pathspec(struct pathspec *pathspec, item->original = prefix; item->nowildcard_len = item->len = strlen(prefix); item->prefix = item->len; - raw[0] = prefix; - raw[1] = NULL; pathspec->nr = 1; - pathspec->_raw = raw; return; } @@ -415,7 +410,6 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->nr = n; ALLOC_ARRAY(pathspec->items, n); item = pathspec->items; - pathspec->_raw = argv; prefixlen = prefix ? strlen(prefix) : 0; for (i = 0; i < n; i++) { @@ -423,7 +417,7 @@ void parse_pathspec(struct pathspec *pathspec, entry = argv[i]; item[i].magic = prefix_pathspec(item + i, &short_magic, - argv + i, flags, + flags, prefix, prefixlen, entry); if ((flags & PATHSPEC_LITERAL_PATH) && !(magic_mask & PATHSPEC_LITERAL)) @@ -457,36 +451,6 @@ void parse_pathspec(struct pathspec *pathspec, } } -/* - * N.B. get_pathspec() is deprecated in favor of the "struct pathspec" - * based interface - see pathspec.c:parse_pathspec(). - * - * Arguments: - * - prefix - a path relative to the root of the working tree - * - pathspec - a list of paths underneath the prefix path - * - * Iterates over pathspec, prepending each path with prefix, - * and return the resulting list. - * - * If pathspec is empty, return a singleton list containing prefix. - * - * If pathspec and prefix are both empty, return an empty list. - * - * This is typically used by built-in commands such as add.c, in order - * to normalize argv arguments provided to the built-in into a list of - * paths to process, all relative to the root of the working tree. - */ -const char **get_pathspec(const char *prefix, const char **pathspec) -{ - struct pathspec ps; - parse_pathspec(&ps, - PATHSPEC_ALL_MAGIC & - ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), - PATHSPEC_PREFER_CWD, - prefix, pathspec); - return ps._raw; -} - void copy_pathspec(struct pathspec *dst, const struct pathspec *src) { *dst = *src; diff --git a/pathspec.h b/pathspec.h index 59809e4793a20e..70a592e9106260 100644 --- a/pathspec.h +++ b/pathspec.h @@ -19,7 +19,6 @@ #define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */ struct pathspec { - const char **_raw; /* get_pathspec() result, not freed by clear_pathspec() */ int nr; unsigned int has_wildcard:1; unsigned int recursive:1; From 8aee769fa12ff0d3a4100c3d0359bc33e49db672 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:01 -0800 Subject: [PATCH 0152/1540] pathspec: copy and free owned memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'original' string entry in a pathspec_item is only duplicated some of the time, instead always make a copy of the original and take ownership of the memory. Since both 'match' and 'original' string entries in a pathspec_item are owned by the pathspec struct, they need to be freed when clearing the pathspec struct (in 'clear_pathspec()') and duplicated when copying the pathspec struct (in 'copy_pathspec()'). Also change the type of 'match' and 'original' to 'char *' in order to more explicitly show the ownership of the memory. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 23 +++++++++++++++++++---- pathspec.h | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pathspec.c b/pathspec.c index 1f918cbaec2e7b..b8faa8f4653bfb 100644 --- a/pathspec.c +++ b/pathspec.c @@ -259,8 +259,9 @@ static unsigned prefix_pathspec(struct pathspec_item *item, } strbuf_addstr(&sb, match); item->original = strbuf_detach(&sb, NULL); - } else - item->original = elt; + } else { + item->original = xstrdup(elt); + } item->len = strlen(item->match); item->prefix = prefixlen; @@ -388,8 +389,8 @@ void parse_pathspec(struct pathspec *pathspec, die("BUG: PATHSPEC_PREFER_CWD requires arguments"); pathspec->items = item = xcalloc(1, sizeof(*item)); - item->match = prefix; - item->original = prefix; + item->match = xstrdup(prefix); + item->original = xstrdup(prefix); item->nowildcard_len = item->len = strlen(prefix); item->prefix = item->len; pathspec->nr = 1; @@ -453,13 +454,27 @@ void parse_pathspec(struct pathspec *pathspec, void copy_pathspec(struct pathspec *dst, const struct pathspec *src) { + int i; + *dst = *src; ALLOC_ARRAY(dst->items, dst->nr); COPY_ARRAY(dst->items, src->items, dst->nr); + + for (i = 0; i < dst->nr; i++) { + dst->items[i].match = xstrdup(src->items[i].match); + dst->items[i].original = xstrdup(src->items[i].original); + } } void clear_pathspec(struct pathspec *pathspec) { + int i; + + for (i = 0; i < pathspec->nr; i++) { + free(pathspec->items[i].match); + free(pathspec->items[i].original); + } free(pathspec->items); pathspec->items = NULL; + pathspec->nr = 0; } diff --git a/pathspec.h b/pathspec.h index 70a592e9106260..49fd823ddfe9eb 100644 --- a/pathspec.h +++ b/pathspec.h @@ -25,8 +25,8 @@ struct pathspec { unsigned magic; int max_depth; struct pathspec_item { - const char *match; - const char *original; + char *match; + char *original; unsigned magic; int len, prefix; int nowildcard_len; From 93f3ddb2a19264f59cda752d5c0ed81d96cdc6cc Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:02 -0800 Subject: [PATCH 0153/1540] pathspec: remove unused variable from unsupported_magic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed unused variable 'n' from the 'unsupported_magic()' function. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pathspec.c b/pathspec.c index b8faa8f4653bfb..b9a3819d65c24d 100644 --- a/pathspec.c +++ b/pathspec.c @@ -333,8 +333,8 @@ static void NORETURN unsupported_magic(const char *pattern, unsigned short_magic) { struct strbuf sb = STRBUF_INIT; - int i, n; - for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + int i; + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { const struct pathspec_magic *m = pathspec_magic + i; if (!(magic & m->bit)) continue; @@ -344,7 +344,6 @@ static void NORETURN unsupported_magic(const char *pattern, strbuf_addf(&sb, "'%c'", m->mnemonic); else strbuf_addf(&sb, "'%s'", m->name); - n++; } /* * We may want to substitute "this command" with a command From 2aee5849c9b3698b65d74bc354f3cee7213add35 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:03 -0800 Subject: [PATCH 0154/1540] pathspec: always show mnemonic and name in unsupported_magic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For better clarity, always show the mnemonic and name of the unsupported magic being used. This lets users have a more clear understanding of what magic feature isn't supported. And if they supplied a mnemonic, the user will be told what its corresponding name is which will allow them to more easily search the man pages for that magic type. This also avoids passing an extra parameter around the pathspec initialization code. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/pathspec.c b/pathspec.c index b9a3819d65c24d..5df364bc69010c 100644 --- a/pathspec.c +++ b/pathspec.c @@ -101,9 +101,7 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen, * the prefix part must always match literally, and a single stupid * string cannot express such a case. */ -static unsigned prefix_pathspec(struct pathspec_item *item, - unsigned *p_short_magic, - unsigned flags, +static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, const char *prefix, int prefixlen, const char *elt) { @@ -210,7 +208,6 @@ static unsigned prefix_pathspec(struct pathspec_item *item, } magic |= short_magic; - *p_short_magic = short_magic; /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ if (noglob_global && !(magic & PATHSPEC_GLOB)) @@ -329,8 +326,7 @@ static int pathspec_item_cmp(const void *a_, const void *b_) } static void NORETURN unsupported_magic(const char *pattern, - unsigned magic, - unsigned short_magic) + unsigned magic) { struct strbuf sb = STRBUF_INIT; int i; @@ -339,9 +335,11 @@ static void NORETURN unsupported_magic(const char *pattern, if (!(magic & m->bit)) continue; if (sb.len) - strbuf_addch(&sb, ' '); - if (short_magic & m->bit) - strbuf_addf(&sb, "'%c'", m->mnemonic); + strbuf_addstr(&sb, ", "); + + if (m->mnemonic) + strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"), + m->name, m->mnemonic); else strbuf_addf(&sb, "'%s'", m->name); } @@ -413,11 +411,9 @@ void parse_pathspec(struct pathspec *pathspec, prefixlen = prefix ? strlen(prefix) : 0; for (i = 0; i < n; i++) { - unsigned short_magic; entry = argv[i]; - item[i].magic = prefix_pathspec(item + i, &short_magic, - flags, + item[i].magic = prefix_pathspec(item + i, flags, prefix, prefixlen, entry); if ((flags & PATHSPEC_LITERAL_PATH) && !(magic_mask & PATHSPEC_LITERAL)) @@ -425,9 +421,7 @@ void parse_pathspec(struct pathspec *pathspec, if (item[i].magic & PATHSPEC_EXCLUDE) nr_exclude++; if (item[i].magic & magic_mask) - unsupported_magic(entry, - item[i].magic & magic_mask, - short_magic); + unsupported_magic(entry, item[i].magic & magic_mask); if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) && has_symlink_leading_path(item[i].match, item[i].len)) { From 5d8f084a5d63501270d6cc0ff2ce04358ca704d6 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:04 -0800 Subject: [PATCH 0155/1540] pathspec: simpler logic to prefix original pathspec elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The logic used to prefix an original pathspec element with 'prefix' magic is more general purpose and can be used for more than just short magic. Remove the extra code paths and rename 'prefix_short_magic' to 'prefix_magic' to better indicate that it can be used in more general situations. Also, slightly change the logic which decides when to prefix the original element in order to prevent a pathspec of "." from getting converted to "" (empty string). Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/pathspec.c b/pathspec.c index 5df364bc69010c..af7f2d01d55333 100644 --- a/pathspec.c +++ b/pathspec.c @@ -74,13 +74,12 @@ static struct pathspec_magic { { PATHSPEC_EXCLUDE, '!', "exclude" }, }; -static void prefix_short_magic(struct strbuf *sb, int prefixlen, - unsigned short_magic) +static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) { int i; strbuf_addstr(sb, ":("); for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) - if (short_magic & pathspec_magic[i].bit) { + if (magic & pathspec_magic[i].bit) { if (sb->buf[sb->len - 1] != '(') strbuf_addch(sb, ','); strbuf_addstr(sb, pathspec_magic[i].name); @@ -109,8 +108,8 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, static int glob_global = -1; static int noglob_global = -1; static int icase_global = -1; - unsigned magic = 0, short_magic = 0, global_magic = 0; - const char *copyfrom = elt, *long_magic_end = NULL; + unsigned magic = 0, element_magic = 0, global_magic = 0; + const char *copyfrom = elt; char *match; int i, pathspec_prefix = -1; @@ -164,7 +163,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { if (strlen(pathspec_magic[i].name) == len && !strncmp(pathspec_magic[i].name, copyfrom, len)) { - magic |= pathspec_magic[i].bit; + element_magic |= pathspec_magic[i].bit; break; } if (starts_with(copyfrom, "prefix:")) { @@ -183,7 +182,6 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, } if (*copyfrom != ')') die(_("Missing ')' at the end of pathspec magic in '%s'"), elt); - long_magic_end = copyfrom; copyfrom++; } else { /* shorthand */ @@ -196,7 +194,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, break; for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) if (pathspec_magic[i].mnemonic == ch) { - short_magic |= pathspec_magic[i].bit; + element_magic |= pathspec_magic[i].bit; break; } if (ARRAY_SIZE(pathspec_magic) <= i) @@ -207,7 +205,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, copyfrom++; } - magic |= short_magic; + magic |= element_magic; /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ if (noglob_global && !(magic & PATHSPEC_GLOB)) @@ -242,18 +240,13 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, * Prefix the pathspec (keep all magic) and assign to * original. Useful for passing to another command. */ - if (flags & PATHSPEC_PREFIX_ORIGIN) { + if ((flags & PATHSPEC_PREFIX_ORIGIN) && + prefixlen && !literal_global) { struct strbuf sb = STRBUF_INIT; - if (prefixlen && !literal_global) { - /* Preserve the actual prefix length of each pattern */ - if (short_magic) - prefix_short_magic(&sb, prefixlen, short_magic); - else if (long_magic_end) { - strbuf_add(&sb, elt, long_magic_end - elt); - strbuf_addf(&sb, ",prefix:%d)", prefixlen); - } else - strbuf_addf(&sb, ":(prefix:%d)", prefixlen); - } + + /* Preserve the actual prefix length of each pattern */ + prefix_magic(&sb, prefixlen, element_magic); + strbuf_addstr(&sb, match); item->original = strbuf_detach(&sb, NULL); } else { From db7e85988f71b13f83f37c30e772d0e9a90d840d Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:05 -0800 Subject: [PATCH 0156/1540] pathspec: factor global magic into its own function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create helper functions to read the global magic environment variables in additon to factoring out the global magic gathering logic into its own function. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 127 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/pathspec.c b/pathspec.c index af7f2d01d55333..77df55da6a2c4c 100644 --- a/pathspec.c +++ b/pathspec.c @@ -87,6 +87,75 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) strbuf_addf(sb, ",prefix:%d)", prefixlen); } +static inline int get_literal_global(void) +{ + static int literal = -1; + + if (literal < 0) + literal = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); + + return literal; +} + +static inline int get_glob_global(void) +{ + static int glob = -1; + + if (glob < 0) + glob = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); + + return glob; +} + +static inline int get_noglob_global(void) +{ + static int noglob = -1; + + if (noglob < 0) + noglob = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); + + return noglob; +} + +static inline int get_icase_global(void) +{ + static int icase = -1; + + if (icase < 0) + icase = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); + + return icase; +} + +static int get_global_magic(int element_magic) +{ + int global_magic = 0; + + if (get_literal_global()) + global_magic |= PATHSPEC_LITERAL; + + /* --glob-pathspec is overridden by :(literal) */ + if (get_glob_global() && !(element_magic & PATHSPEC_LITERAL)) + global_magic |= PATHSPEC_GLOB; + + if (get_glob_global() && get_noglob_global()) + die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); + + if (get_icase_global()) + global_magic |= PATHSPEC_ICASE; + + if ((global_magic & PATHSPEC_LITERAL) && + (global_magic & ~PATHSPEC_LITERAL)) + die(_("global 'literal' pathspec setting is incompatible " + "with all other global pathspec settings")); + + /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ + if (get_noglob_global() && !(element_magic & PATHSPEC_GLOB)) + global_magic |= PATHSPEC_LITERAL; + + return global_magic; +} + /* * Take an element of a pathspec and check for magic signatures. * Append the result to the prefix. Return the magic bitmap. @@ -104,46 +173,12 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, const char *prefix, int prefixlen, const char *elt) { - static int literal_global = -1; - static int glob_global = -1; - static int noglob_global = -1; - static int icase_global = -1; - unsigned magic = 0, element_magic = 0, global_magic = 0; + unsigned magic = 0, element_magic = 0; const char *copyfrom = elt; char *match; int i, pathspec_prefix = -1; - if (literal_global < 0) - literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); - if (literal_global) - global_magic |= PATHSPEC_LITERAL; - - if (glob_global < 0) - glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); - if (glob_global) - global_magic |= PATHSPEC_GLOB; - - if (noglob_global < 0) - noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); - - if (glob_global && noglob_global) - die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); - - - if (icase_global < 0) - icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); - if (icase_global) - global_magic |= PATHSPEC_ICASE; - - if ((global_magic & PATHSPEC_LITERAL) && - (global_magic & ~PATHSPEC_LITERAL)) - die(_("global 'literal' pathspec setting is incompatible " - "with all other global pathspec settings")); - - if (flags & PATHSPEC_LITERAL_PATH) - global_magic = 0; - - if (elt[0] != ':' || literal_global || + if (elt[0] != ':' || get_literal_global() || (flags & PATHSPEC_LITERAL_PATH)) { ; /* nothing to do */ } else if (elt[1] == '(') { @@ -207,15 +242,11 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, magic |= element_magic; - /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ - if (noglob_global && !(magic & PATHSPEC_GLOB)) - global_magic |= PATHSPEC_LITERAL; - - /* --glob-pathspec is overridden by :(literal) */ - if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL)) - global_magic &= ~PATHSPEC_GLOB; - - magic |= global_magic; + /* PATHSPEC_LITERAL_PATH ignores magic */ + if (flags & PATHSPEC_LITERAL_PATH) + magic = PATHSPEC_LITERAL; + else + magic |= get_global_magic(element_magic); if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) @@ -241,7 +272,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, * original. Useful for passing to another command. */ if ((flags & PATHSPEC_PREFIX_ORIGIN) && - prefixlen && !literal_global) { + prefixlen && !get_literal_global()) { struct strbuf sb = STRBUF_INIT; /* Preserve the actual prefix length of each pattern */ @@ -408,9 +439,7 @@ void parse_pathspec(struct pathspec *pathspec, item[i].magic = prefix_pathspec(item + i, flags, prefix, prefixlen, entry); - if ((flags & PATHSPEC_LITERAL_PATH) && - !(magic_mask & PATHSPEC_LITERAL)) - item[i].magic |= PATHSPEC_LITERAL; + if (item[i].magic & PATHSPEC_EXCLUDE) nr_exclude++; if (item[i].magic & magic_mask) From b4bebdce834d9186f6b1646a943612658f4fb91c Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:06 -0800 Subject: [PATCH 0157/1540] pathspec: create parse_short_magic function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Factor out the logic responsible for parsing short magic into its own function. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/pathspec.c b/pathspec.c index 77df55da6a2c4c..1b090184801a3b 100644 --- a/pathspec.c +++ b/pathspec.c @@ -156,6 +156,41 @@ static int get_global_magic(int element_magic) return global_magic; } +/* + * Parse the pathspec element looking for short magic + * + * saves all magic in 'magic' + * returns the position in 'elem' after all magic has been parsed + */ +static const char *parse_short_magic(unsigned *magic, const char *elem) +{ + const char *pos; + + for (pos = elem + 1; *pos && *pos != ':'; pos++) { + char ch = *pos; + int i; + + if (!is_pathspec_magic(ch)) + break; + + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + if (pathspec_magic[i].mnemonic == ch) { + *magic |= pathspec_magic[i].bit; + break; + } + } + + if (ARRAY_SIZE(pathspec_magic) <= i) + die(_("Unimplemented pathspec magic '%c' in '%s'"), + ch, elem); + } + + if (*pos == ':') + pos++; + + return pos; +} + /* * Take an element of a pathspec and check for magic signatures. * Append the result to the prefix. Return the magic bitmap. @@ -220,24 +255,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, copyfrom++; } else { /* shorthand */ - for (copyfrom = elt + 1; - *copyfrom && *copyfrom != ':'; - copyfrom++) { - char ch = *copyfrom; - - if (!is_pathspec_magic(ch)) - break; - for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) - if (pathspec_magic[i].mnemonic == ch) { - element_magic |= pathspec_magic[i].bit; - break; - } - if (ARRAY_SIZE(pathspec_magic) <= i) - die(_("Unimplemented pathspec magic '%c' in '%s'"), - ch, elt); - } - if (*copyfrom == ':') - copyfrom++; + copyfrom = parse_short_magic(&element_magic, elt); } magic |= element_magic; From 8881fde01340e1fff4a3acc17805886b644d18d8 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:07 -0800 Subject: [PATCH 0158/1540] pathspec: create parse_long_magic function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Factor out the logic responsible for parsing long magic into its own function. As well as hoist the prefix check logic outside of the inner loop as there isn't anything that needs to be done after matching "prefix:". Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 92 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/pathspec.c b/pathspec.c index 1b090184801a3b..f6356bde16fcd3 100644 --- a/pathspec.c +++ b/pathspec.c @@ -156,6 +156,60 @@ static int get_global_magic(int element_magic) return global_magic; } +/* + * Parse the pathspec element looking for long magic + * + * saves all magic in 'magic' + * if prefix magic is used, save the prefix length in 'prefix_len' + * returns the position in 'elem' after all magic has been parsed + */ +static const char *parse_long_magic(unsigned *magic, int *prefix_len, + const char *elem) +{ + const char *pos; + const char *nextat; + + for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) { + size_t len = strcspn(pos, ",)"); + int i; + + if (pos[len] == ',') + nextat = pos + len + 1; /* handle ',' */ + else + nextat = pos + len; /* handle ')' and '\0' */ + + if (!len) + continue; + + if (starts_with(pos, "prefix:")) { + char *endptr; + *prefix_len = strtol(pos + 7, &endptr, 10); + if (endptr - pos != len) + die(_("invalid parameter for pathspec magic 'prefix'")); + continue; + } + + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + if (strlen(pathspec_magic[i].name) == len && + !strncmp(pathspec_magic[i].name, pos, len)) { + *magic |= pathspec_magic[i].bit; + break; + } + } + + if (ARRAY_SIZE(pathspec_magic) <= i) + die(_("Invalid pathspec magic '%.*s' in '%s'"), + (int) len, pos, elem); + } + + if (*pos != ')') + die(_("Missing ')' at the end of pathspec magic in '%s'"), + elem); + pos++; + + return pos; +} + /* * Parse the pathspec element looking for short magic * @@ -218,41 +272,9 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, ; /* nothing to do */ } else if (elt[1] == '(') { /* longhand */ - const char *nextat; - for (copyfrom = elt + 2; - *copyfrom && *copyfrom != ')'; - copyfrom = nextat) { - size_t len = strcspn(copyfrom, ",)"); - if (copyfrom[len] == ',') - nextat = copyfrom + len + 1; - else - /* handle ')' and '\0' */ - nextat = copyfrom + len; - if (!len) - continue; - for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { - if (strlen(pathspec_magic[i].name) == len && - !strncmp(pathspec_magic[i].name, copyfrom, len)) { - element_magic |= pathspec_magic[i].bit; - break; - } - if (starts_with(copyfrom, "prefix:")) { - char *endptr; - pathspec_prefix = strtol(copyfrom + 7, - &endptr, 10); - if (endptr - copyfrom != len) - die(_("invalid parameter for pathspec magic 'prefix'")); - /* "i" would be wrong, but it does not matter */ - break; - } - } - if (ARRAY_SIZE(pathspec_magic) <= i) - die(_("Invalid pathspec magic '%.*s' in '%s'"), - (int) len, copyfrom, elt); - } - if (*copyfrom != ')') - die(_("Missing ')' at the end of pathspec magic in '%s'"), elt); - copyfrom++; + copyfrom = parse_long_magic(&element_magic, + &pathspec_prefix, + elt); } else { /* shorthand */ copyfrom = parse_short_magic(&element_magic, elt); From 1b6112c527be607f56a674301b413af16aa186c6 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:08 -0800 Subject: [PATCH 0159/1540] pathspec: create parse_element_magic helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Factor out the logic responsible for the magic in a pathspec element into its own function. Also avoid calling into the parsing functions when `PATHSPEC_LITERAL_PATH` is specified since it causes magic to be ignored and all paths to be treated as literals. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/pathspec.c b/pathspec.c index f6356bde16fcd3..00fcae4e1e9aea 100644 --- a/pathspec.c +++ b/pathspec.c @@ -245,6 +245,19 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) return pos; } +static const char *parse_element_magic(unsigned *magic, int *prefix_len, + const char *elem) +{ + if (elem[0] != ':' || get_literal_global()) + return elem; /* nothing to do */ + else if (elem[1] == '(') + /* longhand */ + return parse_long_magic(magic, prefix_len, elem); + else + /* shorthand */ + return parse_short_magic(magic, elem); +} + /* * Take an element of a pathspec and check for magic signatures. * Append the result to the prefix. Return the magic bitmap. @@ -267,26 +280,16 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, char *match; int i, pathspec_prefix = -1; - if (elt[0] != ':' || get_literal_global() || - (flags & PATHSPEC_LITERAL_PATH)) { - ; /* nothing to do */ - } else if (elt[1] == '(') { - /* longhand */ - copyfrom = parse_long_magic(&element_magic, - &pathspec_prefix, - elt); - } else { - /* shorthand */ - copyfrom = parse_short_magic(&element_magic, elt); - } - - magic |= element_magic; - /* PATHSPEC_LITERAL_PATH ignores magic */ - if (flags & PATHSPEC_LITERAL_PATH) + if (flags & PATHSPEC_LITERAL_PATH) { magic = PATHSPEC_LITERAL; - else + } else { + copyfrom = parse_element_magic(&element_magic, + &pathspec_prefix, + elt); + magic |= element_magic; magic |= get_global_magic(element_magic); + } if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) From 5590215b1392b4f094f60d7db7a3c796b8bc9aae Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:09 -0800 Subject: [PATCH 0160/1540] pathspec: create strip submodule slash helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Factor out the logic responsible for stripping the trailing slash on pathspecs referencing submodules into its own function. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 68 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/pathspec.c b/pathspec.c index 00fcae4e1e9aea..f3a7a1d3374b09 100644 --- a/pathspec.c +++ b/pathspec.c @@ -258,6 +258,44 @@ static const char *parse_element_magic(unsigned *magic, int *prefix_len, return parse_short_magic(magic, elem); } +static void strip_submodule_slash_cheap(struct pathspec_item *item) +{ + if (item->len >= 1 && item->match[item->len - 1] == '/') { + int i = cache_name_pos(item->match, item->len - 1); + + if (i >= 0 && S_ISGITLINK(active_cache[i]->ce_mode)) { + item->len--; + item->match[item->len] = '\0'; + } + } +} + +static void strip_submodule_slash_expensive(struct pathspec_item *item) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + if (item->len <= ce_len || item->match[ce_len] != '/' || + memcmp(ce->name, item->match, ce_len)) + continue; + + if (item->len == ce_len + 1) { + /* strip trailing slash */ + item->len--; + item->match[item->len] = '\0'; + } else { + die(_("Pathspec '%s' is in submodule '%.*s'"), + item->original, ce_len, ce->name); + } + } +} + /* * Take an element of a pathspec and check for magic signatures. * Append the result to the prefix. Return the magic bitmap. @@ -278,7 +316,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, unsigned magic = 0, element_magic = 0; const char *copyfrom = elt; char *match; - int i, pathspec_prefix = -1; + int pathspec_prefix = -1; /* PATHSPEC_LITERAL_PATH ignores magic */ if (flags & PATHSPEC_LITERAL_PATH) { @@ -329,33 +367,11 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, item->len = strlen(item->match); item->prefix = prefixlen; - if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && - (item->len >= 1 && item->match[item->len - 1] == '/') && - (i = cache_name_pos(item->match, item->len - 1)) >= 0 && - S_ISGITLINK(active_cache[i]->ce_mode)) { - item->len--; - match[item->len] = '\0'; - } + if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) + strip_submodule_slash_cheap(item); if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int ce_len = ce_namelen(ce); - - if (!S_ISGITLINK(ce->ce_mode)) - continue; - - if (item->len <= ce_len || match[ce_len] != '/' || - memcmp(ce->name, match, ce_len)) - continue; - if (item->len == ce_len + 1) { - /* strip trailing slash */ - item->len--; - match[item->len] = '\0'; - } else - die (_("Pathspec '%s' is in submodule '%.*s'"), - elt, ce_len, ce->name); - } + strip_submodule_slash_expensive(item); if (magic & PATHSPEC_LITERAL) item->nowildcard_len = item->len; From 4f1bf4d2b48cc2dc755e1a61f7e807e3963b33fa Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:10 -0800 Subject: [PATCH 0161/1540] pathspec: small readability changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A few small changes to improve readability. This is done by grouping related assignments, adding blank lines, ensuring lines are <80 characters, and adding additional comments. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/pathspec.c b/pathspec.c index f3a7a1d3374b09..e53530e7a6fb3f 100644 --- a/pathspec.c +++ b/pathspec.c @@ -67,11 +67,11 @@ static struct pathspec_magic { char mnemonic; /* this cannot be ':'! */ const char *name; } pathspec_magic[] = { - { PATHSPEC_FROMTOP, '/', "top" }, - { PATHSPEC_LITERAL, 0, "literal" }, - { PATHSPEC_GLOB, '\0', "glob" }, - { PATHSPEC_ICASE, '\0', "icase" }, - { PATHSPEC_EXCLUDE, '!', "exclude" }, + { PATHSPEC_FROMTOP, '/', "top" }, + { PATHSPEC_LITERAL, '\0', "literal" }, + { PATHSPEC_GLOB, '\0', "glob" }, + { PATHSPEC_ICASE, '\0', "icase" }, + { PATHSPEC_EXCLUDE, '!', "exclude" }, }; static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) @@ -336,6 +336,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB)) die(_("%s: 'literal' and 'glob' are incompatible"), elt); + /* Create match string which will be used for pathspec matching */ if (pathspec_prefix >= 0) { match = xstrdup(copyfrom); prefixlen = pathspec_prefix; @@ -343,11 +344,16 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, match = xstrdup(copyfrom); prefixlen = 0; } else { - match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom); + match = prefix_path_gently(prefix, prefixlen, + &prefixlen, copyfrom); if (!match) die(_("%s: '%s' is outside repository"), elt, copyfrom); } + item->match = match; + item->len = strlen(item->match); + item->prefix = prefixlen; + /* * Prefix the pathspec (keep all magic) and assign to * original. Useful for passing to another command. @@ -364,8 +370,6 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, } else { item->original = xstrdup(elt); } - item->len = strlen(item->match); - item->prefix = prefixlen; if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) strip_submodule_slash_cheap(item); @@ -373,13 +377,14 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) strip_submodule_slash_expensive(item); - if (magic & PATHSPEC_LITERAL) + if (magic & PATHSPEC_LITERAL) { item->nowildcard_len = item->len; - else { + } else { item->nowildcard_len = simple_length(item->match); if (item->nowildcard_len < prefixlen) item->nowildcard_len = prefixlen; } + item->flags = 0; if (magic & PATHSPEC_GLOB) { /* From 27ec42826e9e5ffa9bcdf0208c1399f62b78c7fb Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 4 Jan 2017 10:04:11 -0800 Subject: [PATCH 0162/1540] pathspec: rename prefix_pathspec to init_pathspec_item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Give a more relevant name to the prefix_pathspec function as it does more than just prefix a pathspec element. Signed-off-by: Brandon Williams Reviewed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pathspec.c b/pathspec.c index e53530e7a6fb3f..ff2509ddd1519b 100644 --- a/pathspec.c +++ b/pathspec.c @@ -297,21 +297,11 @@ static void strip_submodule_slash_expensive(struct pathspec_item *item) } /* - * Take an element of a pathspec and check for magic signatures. - * Append the result to the prefix. Return the magic bitmap. - * - * For now, we only parse the syntax and throw out anything other than - * "top" magic. - * - * NEEDSWORK: This needs to be rewritten when we start migrating - * get_pathspec() users to use the "struct pathspec" interface. For - * example, a pathspec element may be marked as case-insensitive, but - * the prefix part must always match literally, and a single stupid - * string cannot express such a case. + * Perform the initialization of a pathspec_item based on a pathspec element. */ -static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, - const char *prefix, int prefixlen, - const char *elt) +static void init_pathspec_item(struct pathspec_item *item, unsigned flags, + const char *prefix, int prefixlen, + const char *elt) { unsigned magic = 0, element_magic = 0; const char *copyfrom = elt; @@ -329,6 +319,8 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, magic |= get_global_magic(element_magic); } + item->magic = magic; + if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) die("BUG: 'prefix' magic is supposed to be used at worktree's root"); @@ -401,7 +393,6 @@ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags, /* sanity checks, pathspec matchers assume these are sane */ assert(item->nowildcard_len <= item->len && item->prefix <= item->len); - return magic; } static int pathspec_item_cmp(const void *a_, const void *b_) @@ -501,8 +492,7 @@ void parse_pathspec(struct pathspec *pathspec, for (i = 0; i < n; i++) { entry = argv[i]; - item[i].magic = prefix_pathspec(item + i, flags, - prefix, prefixlen, entry); + init_pathspec_item(item + i, flags, prefix, prefixlen, entry); if (item[i].magic & PATHSPEC_EXCLUDE) nr_exclude++; From 007ac544011213045e3905983b4350ffec8f41f7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 9 Jan 2017 01:00:12 -0500 Subject: [PATCH 0163/1540] git_exec_path: do not return the result of getenv() The result of getenv() is not guaranteed by POSIX to last beyond another call to getenv(), or setenv(), etc. We should duplicate the string before returning to the caller to avoid any surprises. We already keep a cached pointer to avoid repeatedly leaking the result of system_path(). We can use the same pointer here to avoid allocating and leaking for each call. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- exec_cmd.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/exec_cmd.c b/exec_cmd.c index eae56fefba9953..31ceb3338334a4 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -68,20 +68,19 @@ void git_set_argv_exec_path(const char *exec_path) /* Returns the highest-priority, location to look for git programs. */ const char *git_exec_path(void) { - const char *env; - static char *system_exec_path; + static char *cached_exec_path; if (argv_exec_path) return argv_exec_path; - env = getenv(EXEC_PATH_ENVIRONMENT); - if (env && *env) { - return env; + if (!cached_exec_path) { + const char *env = getenv(EXEC_PATH_ENVIRONMENT); + if (env && *env) + cached_exec_path = xstrdup(env); + else + cached_exec_path = system_path(GIT_EXEC_PATH); } - - if (!system_exec_path) - system_exec_path = system_path(GIT_EXEC_PATH); - return system_exec_path; + return cached_exec_path; } static void add_path(struct strbuf *out, const char *path) From c6f44e1da5e88e34654ae37c62eea2d08835110b Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 4 Jan 2017 01:27:08 +0530 Subject: [PATCH 0164/1540] t9813: avoid using pipes The exit code of the upstream in a pipe is ignored thus we should avoid using it. By writing out the output of the git command to a file, we can test the exit codes of both the commands. Signed-off-by: Pranit Bauva Acked-by: Luke Diamand Signed-off-by: Junio C Hamano --- t/t9813-git-p4-preserve-users.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh index 76004a5ad667ea..bda222aa0270f3 100755 --- a/t/t9813-git-p4-preserve-users.sh +++ b/t/t9813-git-p4-preserve-users.sh @@ -118,12 +118,12 @@ test_expect_success 'not preserving user with mixed authorship' ' make_change_by_user usernamefile3 Derek derek@example.com && P4EDITOR=cat P4USER=alice P4PASSWD=secret && export P4EDITOR P4USER P4PASSWD && - git p4 commit |\ - grep "git author derek@example.com does not match" && + git p4 commit >actual && + grep "git author derek@example.com does not match" actual && make_change_by_user usernamefile3 Charlie charlie@example.com && - git p4 commit |\ - grep "git author charlie@example.com does not match" && + git p4 commit >actual && + grep "git author charlie@example.com does not match" actual && make_change_by_user usernamefile3 alice alice@example.com && git p4 commit >actual && From 7c44b33f8b67cc6737caa0b812dbd963ca8bbb00 Mon Sep 17 00:00:00 2001 From: Steven Penny Date: Sat, 7 Jan 2017 15:41:10 -0600 Subject: [PATCH 0165/1540] Makefile: POSIX windres When environment variable POSIXLY_CORRECT is set, the "input -o output" syntax is not supported. http://cygwin.com/ml/cygwin/2017-01/msg00036.html Use "-i input -o output" syntax instead. Signed-off-by: Steven Penny Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d861bd9985dd41..a2a12125048ccd 100644 --- a/Makefile +++ b/Makefile @@ -1816,7 +1816,7 @@ $(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEFINES git.res: git.rc GIT-VERSION-FILE $(QUIET_RC)$(RC) \ $(join -DMAJOR= -DMINOR=, $(wordlist 1,2,$(subst -, ,$(subst ., ,$(GIT_VERSION))))) \ - -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" $< -o $@ + -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" -i $< -o $@ # This makes sure we depend on the NO_PERL setting itself. $(SCRIPT_PERL_GEN): GIT-BUILD-OPTIONS From aa38ad2b24c14414c18a440d4cdfc9cd02bbcb96 Mon Sep 17 00:00:00 2001 From: Steven Penny Date: Sun, 8 Jan 2017 00:12:38 -0600 Subject: [PATCH 0166/1540] Makefile: put LIBS after LDFLAGS for imap-send This matches up with the targets git-%, git-http-fetch, git-http-push and git-remote-testsvn. It must be done this way in Cygwin else lcrypto cannot find lgdi32 and lws2_32. Signed-off-by: Steven Penny Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a2a12125048ccd..27afd0f378619c 100644 --- a/Makefile +++ b/Makefile @@ -2046,7 +2046,7 @@ git-%$X: %.o GIT-LDFLAGS $(GITLIBS) git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) $(IMAP_SEND_LDFLAGS) + $(IMAP_SEND_LDFLAGS) $(LIBS) git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ From 7aeb81f1de6a06c7ebc9a881e3a07591d760c9d6 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 9 Jan 2017 10:50:23 -0800 Subject: [PATCH 0167/1540] real_path: prevent redefinition of MAXSYMLINKS The macro 'MAXSYMLINKS' is already defined on macOS and Linux in . If 'MAXSYMLINKS' has already been defined, use the value defined by the OS otherwise default to a value of 32 which is more inline with what is allowed by many systems. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- abspath.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/abspath.c b/abspath.c index 1d56f5ed9f9500..0393213e5af3a4 100644 --- a/abspath.c +++ b/abspath.c @@ -62,7 +62,9 @@ static void get_root_part(struct strbuf *resolved, struct strbuf *remaining) } /* We allow "recursive" symbolic links. Only within reason, though. */ -#define MAXSYMLINKS 5 +#ifndef MAXSYMLINKS +#define MAXSYMLINKS 32 +#endif /* * Return the real path (i.e., absolute path, with symlinks resolved From 0b9864aa28ba08d7fb901afee1a75a15e4ad431b Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 9 Jan 2017 10:50:24 -0800 Subject: [PATCH 0168/1540] real_path: set errno when max number of symlinks is exceeded Set errno to ELOOP when the maximum number of symlinks is exceeded, as would be done by other symlink-resolving functions. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- abspath.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/abspath.c b/abspath.c index 0393213e5af3a4..fce40fddcc3b68 100644 --- a/abspath.c +++ b/abspath.c @@ -141,6 +141,8 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path, strbuf_reset(&symlink); if (num_symlinks++ > MAXSYMLINKS) { + errno = ELOOP; + if (die_on_error) die("More than %d nested symlinks " "on path '%s'", MAXSYMLINKS, path); From 2b296c93d49d65303a4ce291225c8755eeab1ff8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 6 Jan 2017 20:16:24 -0500 Subject: [PATCH 0169/1540] execv_dashed_external: use child_process struct When we run a dashed external, we use the one-liner run_command_v_opt() to do so. Let's switch to using a child_process struct, which has two advantages: 1. We can drop all of the allocation and cleanup code for building our custom argv array, and just rely on the builtin argv_array (at the minor cost of doing a few extra mallocs). 2. We have access to the complete range of child_process options, not just the ones that the "_opt()" form can forward. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/git.c b/git.c index dce529fcbfd6e5..d0e04d5c97bd64 100644 --- a/git.c +++ b/git.c @@ -575,8 +575,7 @@ static void handle_builtin(int argc, const char **argv) static void execv_dashed_external(const char **argv) { - struct strbuf cmd = STRBUF_INIT; - const char *tmp; + struct child_process cmd = CHILD_PROCESS_INIT; int status; if (get_super_prefix()) @@ -586,30 +585,20 @@ static void execv_dashed_external(const char **argv) use_pager = check_pager_config(argv[0]); commit_pager_choice(); - strbuf_addf(&cmd, "git-%s", argv[0]); + argv_array_pushf(&cmd.args, "git-%s", argv[0]); + argv_array_pushv(&cmd.args, argv + 1); + cmd.clean_on_exit = 1; + cmd.silent_exec_failure = 1; - /* - * argv[0] must be the git command, but the argv array - * belongs to the caller, and may be reused in - * subsequent loop iterations. Save argv[0] and - * restore it on error. - */ - tmp = argv[0]; - argv[0] = cmd.buf; - - trace_argv_printf(argv, "trace: exec:"); + trace_argv_printf(cmd.args.argv, "trace: exec:"); /* * if we fail because the command is not found, it is * OK to return. Otherwise, we just pass along the status code. */ - status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT); + status = run_command(&cmd); if (status >= 0 || errno != ENOENT) exit(status); - - argv[0] = tmp; - - strbuf_release(&cmd); } static int run_argv(int *argcp, const char ***argv) From 246f0edec0b789ccfeebcf7fef85417b7cb04425 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 6 Jan 2017 20:17:48 -0500 Subject: [PATCH 0170/1540] execv_dashed_external: stop exiting with negative code When we try to exec a git sub-command, we pass along the status code from run_command(). But that may return -1 if we ran into an error with pipe() or execve(). This tends to work (and end up as 255 due to twos-complement wraparound and truncation), but in general it's probably a good idea to avoid negative exit codes for portability. We can easily translate to the normal generic "128" code we get when syscalls cause us to die. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/git.c b/git.c index d0e04d5c97bd64..bc2f2a7ec9dd3f 100644 --- a/git.c +++ b/git.c @@ -593,12 +593,16 @@ static void execv_dashed_external(const char **argv) trace_argv_printf(cmd.args.argv, "trace: exec:"); /* - * if we fail because the command is not found, it is - * OK to return. Otherwise, we just pass along the status code. + * If we fail because the command is not found, it is + * OK to return. Otherwise, we just pass along the status code, + * or our usual generic code if we were not even able to exec + * the program. */ status = run_command(&cmd); - if (status >= 0 || errno != ENOENT) + if (status >= 0) exit(status); + else if (errno != ENOENT) + exit(128); } static int run_argv(int *argcp, const char ***argv) From 46df6906f3aaf74dafe2026b028c8c5c1a0d5f58 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 6 Jan 2017 20:22:23 -0500 Subject: [PATCH 0171/1540] execv_dashed_external: wait for child on signal death When you hit ^C to interrupt a git command going to a pager, this usually leaves the pager running. But when a dashed external is in use, the pager ends up in a funny state and quits (but only after eating one more character from the terminal!). This fixes it. Explaining the reason will require a little background. When git runs a pager, it's important for the git process to hang around and wait for the pager to finish, even though it has no more data to feed it. This is because git spawns the pager as a child, and thus the git process is the session leader on the terminal. After it dies, the pager will finish its current read from the terminal (eating the one character), and then get EIO trying to read again. When you hit ^C, that sends SIGINT to git and to the pager, and it's a similar situation. The pager ignores it, but the git process needs to hang around until the pager is done. We addressed that long ago in a3da882120 (pager: do wait_for_pager on signal death, 2009-01-22). But when you have a dashed external (or an alias pointing to a builtin, which will re-exec git for the builtin), there's an extra process in the mix. For instance, running: $ git -c alias.l=log l will end up with a process tree like: git (parent) \ git-log (child) \ less (pager) If you hit ^C, SIGINT goes to all of them. The pager ignores it, and the child git process will end up in wait_for_pager(). But the parent git process will die, and the usual EIO trouble happens. So we really want the parent git process to wait_for_pager(), but of course it doesn't know anything about the pager at all, since it was started by the child. However, we can have it wait on the git-log child, which in turn is waiting on the pager. And that's what this patch does. There are a few design decisions here worth explaining: 1. The new feature is attached to run-command's clean_on_exit feature. Partly this is convenience, since that feature already has a signal handler that deals with child cleanup. But it's also a meaningful connection. The main reason that dashed externals use clean_on_exit is to bind the two processes together. If somebody kills the parent with a signal, we propagate that to the child (in this instance with SIGINT, we do propagate but it doesn't matter because the original signal went to the whole process group). Likewise, we do not want the parent to go away until the child has done so. In a traditional Unix world, we'd probably accomplish this binding by just having the parent execve() the child directly. But since that doesn't work on Windows, everything goes through run_command's more spawn-like interface. 2. We do _not_ automatically waitpid() on any clean_on_exit children. For dashed externals this makes sense; we know that the parent is doing nothing but waiting for the child to exit anyway. But with other children, it's possible that the child, after getting the signal, could be waiting on the parent to do something (like closing a descriptor). If we were to wait on such a child, we'd end up in a deadlock. So this errs on the side of caution, and lets callers enable the feature explicitly. 3. When we send children the cleanup signal, we send all the signals first, before waiting on any children. This is to avoid the case where one child might be waiting on another one to exit, causing a deadlock. We inform all of them that it's time to die before reaping any. In practice, there is only ever one dashed external run from a given process, so this doesn't matter much now. But it future-proofs us if other callers start using the wait_after_clean mechanism. There's no automated test here, because it would end up racy and unportable. But it's easy to reproduce the situation by running the log command given above and hitting ^C. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git.c | 1 + run-command.c | 19 +++++++++++++++++++ run-command.h | 1 + 3 files changed, 21 insertions(+) diff --git a/git.c b/git.c index bc2f2a7ec9dd3f..c8fe6637dfcab2 100644 --- a/git.c +++ b/git.c @@ -588,6 +588,7 @@ static void execv_dashed_external(const char **argv) argv_array_pushf(&cmd.args, "git-%s", argv[0]); argv_array_pushv(&cmd.args, argv + 1); cmd.clean_on_exit = 1; + cmd.wait_after_clean = 1; cmd.silent_exec_failure = 1; trace_argv_printf(cmd.args.argv, "trace: exec:"); diff --git a/run-command.c b/run-command.c index ca905a9e8038dd..73bfba7ef94e67 100644 --- a/run-command.c +++ b/run-command.c @@ -29,6 +29,8 @@ static int installed_child_cleanup_handler; static void cleanup_children(int sig, int in_signal) { + struct child_to_clean *children_to_wait_for = NULL; + while (children_to_clean) { struct child_to_clean *p = children_to_clean; children_to_clean = p->next; @@ -45,6 +47,23 @@ static void cleanup_children(int sig, int in_signal) } kill(p->pid, sig); + + if (p->process->wait_after_clean) { + p->next = children_to_wait_for; + children_to_wait_for = p; + } else { + if (!in_signal) + free(p); + } + } + + while (children_to_wait_for) { + struct child_to_clean *p = children_to_wait_for; + children_to_wait_for = p->next; + + while (waitpid(p->pid, NULL, 0) < 0 && errno == EINTR) + ; /* spin waiting for process exit or error */ + if (!in_signal) free(p); } diff --git a/run-command.h b/run-command.h index dd1c78c28db90b..4fa8f65adbdcea 100644 --- a/run-command.h +++ b/run-command.h @@ -43,6 +43,7 @@ struct child_process { unsigned stdout_to_stderr:1; unsigned use_shell:1; unsigned clean_on_exit:1; + unsigned wait_after_clean:1; void (*clean_on_exit_handler)(struct child_process *process); void *clean_on_exit_handler_cbdata; }; From 637666c82258a2a11424791ad83b3a43cae101a4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:08 +0100 Subject: [PATCH 0172/1540] sequencer: avoid unnecessary curly braces This was noticed while addressing Junio Hamano's concern that some "else" operators were on separate lines than the preceding closing brace. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 9adb7bbf1d4815..23793db08b674d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -632,9 +632,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } discard_cache(); - if (!commit->parents) { + if (!commit->parents) parent = NULL; - } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; From a70d8f8067640070d89d0764d0dee491f8e0bd13 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:14 +0100 Subject: [PATCH 0173/1540] sequencer: move "else" keyword onto the same line as preceding brace It is the current coding style of the Git project to write if (...) { ... } else { ... } instead of putting the closing brace and the "else" keyword on separate lines. Pointed out by Junio Hamano. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 23793db08b674d..3eededcb980c9a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1070,8 +1070,7 @@ static int create_seq_dir(void) error(_("a cherry-pick or revert is already in progress")); advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; - } - else if (mkdir(git_path_seq_dir(), 0777) < 0) + } else if (mkdir(git_path_seq_dir(), 0777) < 0) return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; From 23aa51420cca7bee854f54e970206c00f3be0c86 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:20 +0100 Subject: [PATCH 0174/1540] sequencer: use a helper to find the commit message It is actually not safe to look for a commit message by looking for the first empty line and skipping it. The find_commit_subject() function looks more carefully, so let's use it. Since we are interested in the entire commit message, we re-compute the string length after verifying that the commit subject is not empty (in which case the entire commit message would be empty, something that should not happen but that we want to handle gracefully). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3eededcb980c9a..720857bedaa051 100644 --- a/sequencer.c +++ b/sequencer.c @@ -703,14 +703,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, next = commit; next_label = msg.label; - /* - * Append the commit log message to msgbuf; it starts - * after the tree, parent, author, committer - * information followed by "\n\n". - */ - p = strstr(msg.message, "\n\n"); - if (p) - strbuf_addstr(&msgbuf, skip_blank_lines(p + 2)); + /* Append the commit log message to msgbuf. */ + if (find_commit_subject(msg.message, &p)) + strbuf_addstr(&msgbuf, p); if (opts->record_origin) { if (!has_conforming_footer(&msgbuf, NULL, 0)) From 845839575d37da825746816b24376c7799ef1105 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:28 +0100 Subject: [PATCH 0175/1540] sequencer: support a new action: 'interactive rebase' This patch introduces a new action for the sequencer. It really does not do a whole lot of its own right now, but lays the ground work for patches to come. The intention, of course, is to finally make the sequencer the work horse of the interactive rebase (the original idea behind the "sequencer" concept). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 36 ++++++++++++++++++++++++++++++++---- sequencer.h | 3 ++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 720857bedaa051..690460bc6759ab 100644 --- a/sequencer.c +++ b/sequencer.c @@ -30,6 +30,14 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") +static GIT_PATH_FUNC(rebase_path, "rebase-merge") +/* + * The file containing rebase commands, comments, and empty lines. + * This file is created by "git rebase -i" then edited by the user. As + * the lines are processed, they are removed from the front of this + * file and written to the tail of 'done'. + */ +static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -42,19 +50,22 @@ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") -/* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return 0; + return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path_todo(); return git_path_todo_file(); } @@ -122,7 +133,15 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); + switch (opts->action) { + case REPLAY_REVERT: + return N_("revert"); + case REPLAY_PICK: + return N_("cherry-pick"); + case REPLAY_INTERACTIVE_REBASE: + return N_("rebase -i"); + } + die(_("Unknown action: %d"), opts->action); } struct commit_message { @@ -364,7 +383,9 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + /* TRANSLATORS: %s will be "revert", "cherry-pick" or + * "rebase -i". + */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -1198,6 +1219,13 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; + /* + * rebase -i writes "git-rebase-todo" without the currently executing + * command, appending it to "done" instead. + */ + if (is_rebase_i(opts)) + next++; + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); diff --git a/sequencer.h b/sequencer.h index 7a513c576bdccf..cb21cfddeec5db 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,7 +7,8 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK + REPLAY_PICK, + REPLAY_INTERACTIVE_REBASE }; struct replay_opts { From 25c4366782ea941baad3b09d4b9ef63996f1e3b1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:38 +0100 Subject: [PATCH 0176/1540] sequencer (rebase -i): implement the 'noop' command The 'noop' command is probably the most boring of all rebase -i commands to support in the sequencer. Which makes it an excellent candidate for this first stab to add support for rebase -i's commands to the sequencer. For the moment, let's also treat empty lines and commented-out lines as 'noop'; We will refine that handling later in this patch series. To make it easier to identify "classes" of todo_commands (such as: determine whether a command is pick-like, i.e. handles a single commit), let's enforce a certain order of said commands. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 690460bc6759ab..84f18e64e9ee68 100644 --- a/sequencer.c +++ b/sequencer.c @@ -607,14 +607,23 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * below, it is also divided into several sections that matter. When adding + * new commands, make sure you add it in the right section. + */ enum todo_command { + /* commands that handle commits */ TODO_PICK = 0, - TODO_REVERT + TODO_REVERT, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP }; static const char *todo_command_strings[] = { "pick", - "revert" + "revert", + "noop" }; static const char *command_to_string(const enum todo_command command) @@ -624,6 +633,10 @@ static const char *command_to_string(const enum todo_command command) die("Unknown command: %d", command); } +static int is_noop(const enum todo_command command) +{ + return TODO_NOOP <= (size_t)command; +} static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts) @@ -879,6 +892,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); + if (bol == eol || *bol == '\r' || *bol == comment_line_char) { + item->command = TODO_NOOP; + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; @@ -887,6 +908,13 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) if (i >= ARRAY_SIZE(todo_command_strings)) return -1; + if (item->command == TODO_NOOP) { + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) @@ -1289,7 +1317,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(item->command, item->commit, opts); + if (item->command <= TODO_REVERT) + res = do_pick_commit(item->command, item->commit, + opts); + else if (!is_noop(item->command)) + return error(_("unknown command %d"), item->command); + todo_list->current++; if (res) return res; From 56dc3ab04bf0f7bb8c73ebbba47469bdf8be8ac4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:43 +0100 Subject: [PATCH 0177/1540] sequencer (rebase -i): implement the 'edit' command This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 84f18e64e9ee68..b138a3906cf386 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,6 +17,7 @@ #include "argv-array.h" #include "quote.h" #include "trailer.h" +#include "log-tree.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -44,6 +45,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * When an "edit" rebase command is being processed, the SHA1 of the + * commit to be edited is recorded in this file. When "git rebase + * --continue" is executed, if there are any staged changes then they + * will be amended to the HEAD commit, but only provided the HEAD + * commit is still the commit to be edited. When any other rebase + * command is processed, this file is deleted. + */ +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") +/* + * When we stop at a given patch via the "edit" command, this file contains + * the abbreviated commit name of the corresponding patch. + */ +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -616,6 +631,7 @@ enum todo_command { /* commands that handle commits */ TODO_PICK = 0, TODO_REVERT, + TODO_EDIT, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -623,6 +639,7 @@ enum todo_command { static const char *todo_command_strings[] = { "pick", "revert", + "edit", "noop" }; @@ -1302,9 +1319,87 @@ static int save_opts(struct replay_opts *opts) return res; } +static int make_patch(struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + struct rev_info log_tree_opt; + const char *subject, *p; + int res = 0; + + p = short_commit_name(commit); + if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) + return -1; + + strbuf_addf(&buf, "%s/patch", get_dir(opts)); + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.abbrev = 0; + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; + log_tree_opt.disable_stdin = 1; + log_tree_opt.no_commit_id = 1; + log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; + if (!log_tree_opt.diffopt.file) + res |= error_errno(_("could not open '%s'"), buf.buf); + else { + res |= log_tree_commit(&log_tree_opt, commit); + fclose(log_tree_opt.diffopt.file); + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "%s/message", get_dir(opts)); + if (!file_exists(buf.buf)) { + const char *commit_buffer = get_commit_buffer(commit, NULL); + find_commit_subject(commit_buffer, &subject); + res |= write_message(subject, strlen(subject), buf.buf, 1); + unuse_commit_buffer(commit, commit_buffer); + } + strbuf_release(&buf); + + return res; +} + +static int intend_to_amend(void) +{ + unsigned char head[20]; + char *p; + + if (get_sha1("HEAD", head)) + return error(_("cannot read HEAD")); + + p = sha1_to_hex(head); + return write_message(p, strlen(p), rebase_path_amend(), 1); +} + +static int error_with_patch(struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, int exit_code, int to_amend) +{ + if (make_patch(commit, opts)) + return -1; + + if (to_amend) { + if (intend_to_amend()) + return -1; + + fprintf(stderr, "You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n", gpg_sign_opt_quoted(opts)); + } else if (exit_code) + fprintf(stderr, "Could not apply %s... %.*s\n", + short_commit_name(commit), subject_len, subject); + + return exit_code; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res; + int res = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1317,10 +1412,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_REVERT) + if (item->command <= TODO_EDIT) { res = do_pick_commit(item->command, item->commit, opts); - else if (!is_noop(item->command)) + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) + warning(_("stopped at %s... %.*s"), + short_commit_name(commit), + item->arg_len, item->arg); + return error_with_patch(commit, + item->arg, item->arg_len, opts, res, + !res); + } + } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); todo_list->current++; @@ -1328,6 +1433,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) return res; } + if (is_rebase_i(opts)) { + /* Stopped in the middle, as planned? */ + if (todo_list->current < todo_list->nr) + return 0; + } + /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory From 311af5266b27523742e8be9d8297ea475137cfb5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:47 +0100 Subject: [PATCH 0178/1540] sequencer (rebase -i): implement the 'exec' command The 'exec' command is a little special among rebase -i's commands, as it does *not* have a SHA-1 as first parameter. Instead, everything after the `exec` command is treated as command-line to execute. Let's reuse the arg/arg_len fields of the todo_item structure (which hold the oneline for pick/edit commands) to point to the command-line. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/sequencer.c b/sequencer.c index b138a3906cf386..e9c10d7fe55057 100644 --- a/sequencer.c +++ b/sequencer.c @@ -18,6 +18,7 @@ #include "quote.h" #include "trailer.h" #include "log-tree.h" +#include "wt-status.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -632,6 +633,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + /* commands that do something else than handling a single commit */ + TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -640,6 +643,7 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "exec", "noop" }; @@ -938,6 +942,12 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return -1; bol += padding; + if (item->command == TODO_EXEC) { + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -1397,6 +1407,46 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int do_exec(const char *command_line) +{ + const char *child_argv[] = { NULL, NULL }; + int dirty, status; + + fprintf(stderr, "Executing: %s\n", command_line); + child_argv[0] = command_line; + status = run_command_v_opt(child_argv, RUN_USING_SHELL); + + /* force re-reading of the cache */ + if (discard_cache() < 0 || read_cache() < 0) + return error(_("could not read index")); + + dirty = require_clean_work_tree("rebase", NULL, 1, 1); + + if (status) { + warning(_("execution failed: %s\n%s" + "You can fix the problem, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), + command_line, + dirty ? N_("and made changes to the index and/or the " + "working tree\n") : ""); + if (status == 127) + /* command not found */ + status = 1; + } else if (dirty) { + warning(_("execution succeeded: %s\nbut " + "left changes to the index and/or the working tree\n" + "Commit or stash your changes, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), command_line); + status = 1; + } + + return status; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1425,6 +1475,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + } else if (item->command == TODO_EXEC) { + char *end_of_arg = (char *)(item->arg + item->arg_len); + int saved = *end_of_arg; + + *end_of_arg = '\0'; + res = do_exec(item->arg); + *end_of_arg = saved; } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); From 556907f1e1057292e5cfc1f14e68167b43ff4761 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:26:53 +0100 Subject: [PATCH 0179/1540] sequencer (rebase -i): learn about the 'verbose' mode When calling `git rebase -i -v`, the user wants to see some statistics after the commits were rebased. Let's show some. The strbuf we use to perform that task will be used for other things in subsequent commits, hence it is declared and initialized in a wider scope than strictly needed here. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 28 ++++++++++++++++++++++++++++ sequencer.h | 1 + 2 files changed, 29 insertions(+) diff --git a/sequencer.c b/sequencer.c index e9c10d7fe55057..ddc4d144d7d58d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -65,6 +65,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") +static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1088,6 +1090,9 @@ static int read_populate_opts(struct replay_opts *opts) } strbuf_release(&buf); + if (file_exists(rebase_path_verbose())) + opts->verbose = 1; + return 0; } @@ -1491,9 +1496,32 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + + if (opts->verbose) { + struct rev_info log_tree_opt; + struct object_id orig, head; + + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = + DIFF_FORMAT_DIFFSTAT; + log_tree_opt.disable_stdin = 1; + + if (read_oneliner(&buf, rebase_path_orig_head(), 0) && + !get_sha1(buf.buf, orig.hash) && + !get_sha1("HEAD", head.hash)) { + diff_tree_sha1(orig.hash, head.hash, + "", &log_tree_opt.diffopt); + log_tree_diff_flush(&log_tree_opt); + } + } + strbuf_release(&buf); } /* diff --git a/sequencer.h b/sequencer.h index cb21cfddeec5db..f885b68395f4bf 100644 --- a/sequencer.h +++ b/sequencer.h @@ -24,6 +24,7 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; + int verbose; int mainline; From 1df6df0c18d068a80010e137b5f1d1dcc2bda600 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:00 +0100 Subject: [PATCH 0180/1540] sequencer (rebase -i): write the 'done' file In the interactive rebase, commands that were successfully processed are not simply discarded, but appended to the 'done' file instead. This is used e.g. to display the current state to the user in the output of `git status` or the progress. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sequencer.c b/sequencer.c index ddc4d144d7d58d..8ea3d6aa949287 100644 --- a/sequencer.c +++ b/sequencer.c @@ -40,6 +40,12 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * file and written to the tail of 'done'. */ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +/* + * The rebase command lines that have already been processed. A line + * is moved here when it is first handled, before any associated user + * actions. + */ +static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -1296,6 +1302,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'."), todo_path); + + if (is_rebase_i(opts)) { + const char *done_path = rebase_path_done(); + int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + int prev_offset = !next ? 0 : + todo_list->items[next - 1].offset_in_buf; + + if (fd >= 0 && offset > prev_offset && + write_in_full(fd, todo_list->buf.buf + prev_offset, + offset - prev_offset) < 0) { + close(fd); + return error_errno(_("could not write to '%s'"), + done_path); + } + if (fd >= 0) + close(fd); + } return 0; } From 6e98de72c03a5bad5ecab1e328e91ef329ba1f41 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:07 +0100 Subject: [PATCH 0181/1540] sequencer (rebase -i): add support for the 'fixup' and 'squash' commands This is a huge patch, and at the same time a huge step forward to execute the performance-critical parts of the interactive rebase in a builtin command. Since 'fixup' and 'squash' are not only similar, but also need to know about each other (we want to reduce a series of fixups/squashes into a single, final commit message edit, from the user's point of view), we really have to implement them both at the same time. Most of the actual work is done by the existing code path that already handles the "pick" and the "edit" commands; We added support for other features (e.g. to amend the commit message) in the patches leading up to this one, yet there are still quite a few bits in this patch that simply would not make sense as individual patches (such as: determining whether there was anything to "fix up" in the "todo" script, etc). In theory, it would be possible to reuse the fast-forward code path also for the fixup and the squash code paths, but in practice this would make the code less readable. The end result cannot be fast-forwarded anyway, therefore let's just extend the cherry-picking code path for now. Since the sequencer parses the entire `git-rebase-todo` script in one go, fixup or squash commands without a preceding pick can be reported early (in git-rebase--interactive, we could only report such errors just before executing the fixup/squash). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 217 insertions(+), 10 deletions(-) diff --git a/sequencer.c b/sequencer.c index 8ea3d6aa949287..6a939a10bdf661 100644 --- a/sequencer.c +++ b/sequencer.c @@ -46,6 +46,35 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The commit message that is planned to be used for any changes that + * need to be committed following a user interaction. + */ +static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") +/* + * The file into which is accumulated the suggested commit message for + * squash/fixup commands. When the first of a series of squash/fixups + * is seen, the file is created and the commit message from the + * previous commit and from the first squash/fixup commit are written + * to it. The commit message for each subsequent squash/fixup commit + * is appended to the file as it is processed. + * + * The first line of the file is of the form + * # This is a combination of $count commits. + * where $count is the number of commits whose messages have been + * written to the file so far (including the initial "pick" commit). + * Each time that a commit message is processed, this line is read and + * updated. It is deleted just before the combined commit is made. + */ +static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") +/* + * If the current series of squash/fixups has not yet included a squash + * command, then this file exists and holds the commit message of the + * original "pick" commit. (If the series ends without a "squash" + * command, then this can be used as the commit message of the combined + * commit without opening the editor.) + */ +static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -641,6 +670,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_FIXUP, + TODO_SQUASH, /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ @@ -651,6 +682,8 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "fixup", + "squash", "exec", "noop" }; @@ -667,15 +700,114 @@ static int is_noop(const enum todo_command command) return TODO_NOOP <= (size_t)command; } +static int is_fixup(enum todo_command command) +{ + return command == TODO_FIXUP || command == TODO_SQUASH; +} + +static int update_squash_messages(enum todo_command command, + struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + int count, res; + const char *message, *body; + + if (file_exists(rebase_path_squash_msg())) { + struct strbuf header = STRBUF_INIT; + char *eol, *p; + + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + return error(_("could not read '%s'"), + rebase_path_squash_msg()); + + p = buf.buf + 1; + eol = strchrnul(buf.buf, '\n'); + if (buf.buf[0] != comment_line_char || + (p += strcspn(p, "0123456789\n")) == eol) + return error(_("unexpected 1st line of squash message:" + "\n\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + count = strtol(p, NULL, 10); + + if (count < 1) + return error(_("invalid 1st line of squash message:\n" + "\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + + strbuf_addf(&header, "%c ", comment_line_char); + strbuf_addf(&header, + _("This is a combination of %d commits."), ++count); + strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); + strbuf_release(&header); + } else { + unsigned char head[20]; + struct commit *head_commit; + const char *head_message, *body; + + if (get_sha1("HEAD", head)) + return error(_("need a HEAD to fixup")); + if (!(head_commit = lookup_commit_reference(head))) + return error(_("could not read HEAD")); + if (!(head_message = get_commit_buffer(head_commit, NULL))) + return error(_("could not read HEAD's commit message")); + + find_commit_subject(head_message, &body); + if (write_message(body, strlen(body), + rebase_path_fixup_msg(), 0)) { + unuse_commit_buffer(head_commit, head_message); + return error(_("cannot write '%s'"), + rebase_path_fixup_msg()); + } + + count = 2; + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, _("This is a combination of %d commits."), + count); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addstr(&buf, _("This is the 1st commit message:")); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + + unuse_commit_buffer(head_commit, head_message); + } + + if (!(message = get_commit_buffer(commit, NULL))) + return error(_("could not read commit message of %s"), + oid_to_hex(&commit->object.oid)); + find_commit_subject(message, &body); + + if (command == TODO_SQUASH) { + unlink(rebase_path_fixup_msg()); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("This is the commit message #%d:"), count); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + } else if (command == TODO_FIXUP) { + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("The commit message #%d will be skipped:"), + count); + strbuf_addstr(&buf, "\n\n"); + strbuf_add_commented_lines(&buf, body, strlen(body)); + } else + return error(_("unknown command: %d"), command); + unuse_commit_buffer(commit, message); + + res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); + strbuf_release(&buf); + return res; +} + static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts) + struct replay_opts *opts, int final_fixup) { + int edit = opts->edit, cleanup_commit_message = 0; + const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res, unborn = 0, amend = 0, allow; if (opts->no_commit) { /* @@ -720,7 +852,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (opts->allow_ff && + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || (!parent && unborn))) return fast_forward_to(commit->object.oid.hash, head, unborn, opts); @@ -779,6 +911,27 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } + if (is_fixup(command)) { + if (update_squash_messages(command, commit, opts)) + return -1; + amend = 1; + if (!final_fixup) + msg_file = rebase_path_squash_msg(); + else if (file_exists(rebase_path_fixup_msg())) { + cleanup_commit_message = 1; + msg_file = rebase_path_fixup_msg(); + } else { + const char *dest = git_path("SQUASH_MSG"); + unlink(dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), dest); + unlink(git_path("MERGE_MSG")); + msg_file = dest; + edit = 1; + } + } + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); @@ -834,8 +987,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0, 0); + res = run_git_commit(msg_file, opts, allow, edit, amend, + cleanup_commit_message); + + if (!res && final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } leave: free_message(commit, &msg); @@ -976,7 +1134,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; - int i, res = 0; + int i, res = 0, fixup_okay = file_exists(rebase_path_done()); for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -991,8 +1149,16 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = -1; + item->command = TODO_NOOP; } + + if (fixup_okay) + ; /* do nothing */ + else if (is_fixup(item->command)) + return error(_("cannot '%s' without a previous commit"), + command_to_string(item->command)); + else if (!is_noop(item->command)) + fixup_okay = 1; } if (!todo_list->nr) return error(_("no commits parsed.")); @@ -1435,6 +1601,20 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int error_failed_squash(struct commit *commit, + struct replay_opts *opts, int subject_len, const char *subject) +{ + if (rename(rebase_path_squash_msg(), rebase_path_message())) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), rebase_path_message()); + unlink(rebase_path_fixup_msg()); + unlink(git_path("MERGE_MSG")); + if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) + return error(_("could not copy '%s' to '%s'"), + rebase_path_message(), git_path("MERGE_MSG")); + return error_with_patch(commit, subject, subject_len, opts, 1, 0); +} + static int do_exec(const char *command_line) { const char *child_argv[] = { NULL, NULL }; @@ -1475,6 +1655,21 @@ static int do_exec(const char *command_line) return status; } +static int is_final_fixup(struct todo_list *todo_list) +{ + int i = todo_list->current; + + if (!is_fixup(todo_list->items[i].command)) + return 0; + + while (++i < todo_list->nr) + if (is_fixup(todo_list->items[i].command)) + return 0; + else if (!is_noop(todo_list->items[i].command)) + break; + return 1; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1490,9 +1685,15 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_EDIT) { + if (is_rebase_i(opts)) { + unlink(rebase_path_message()); + unlink(rebase_path_author_script()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + } + if (item->command <= TODO_SQUASH) { res = do_pick_commit(item->command, item->commit, - opts); + opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) @@ -1503,6 +1704,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(item->commit, opts, + item->arg_len, item->arg); + } } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; @@ -1601,7 +1808,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts); + TODO_PICK : TODO_REVERT, cmit, opts, 0); } int sequencer_pick_revisions(struct replay_opts *opts) From 414697a9d82079851a4623b01d6b3a125919d026 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:15 +0100 Subject: [PATCH 0182/1540] sequencer (rebase -i): implement the short commands For users' convenience, most rebase commands can be abbreviated, e.g. 'p' instead of 'pick' and 'x' instead of 'exec'. Let's teach the sequencer to handle those abbreviated commands just fine. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/sequencer.c b/sequencer.c index 6a939a10bdf661..29b944d724a4f9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -678,20 +678,23 @@ enum todo_command { TODO_NOOP }; -static const char *todo_command_strings[] = { - "pick", - "revert", - "edit", - "fixup", - "squash", - "exec", - "noop" +static struct { + char c; + const char *str; +} todo_command_info[] = { + { 'p', "pick" }, + { 0, "revert" }, + { 'e', "edit" }, + { 'f', "fixup" }, + { 's', "squash" }, + { 'x', "exec" }, + { 0, "noop" } }; static const char *command_to_string(const enum todo_command command) { - if ((size_t)command < ARRAY_SIZE(todo_command_strings)) - return todo_command_strings[command]; + if ((size_t)command < ARRAY_SIZE(todo_command_info)) + return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1087,12 +1090,16 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) - if (skip_prefix(bol, todo_command_strings[i], &bol)) { + for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + if (skip_prefix(bol, todo_command_info[i].str, &bol)) { + item->command = i; + break; + } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + bol++; item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_strings)) + if (i >= ARRAY_SIZE(todo_command_info)) return -1; if (item->command == TODO_NOOP) { @@ -1287,7 +1294,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_strings[command]; + const char *command_string = todo_command_info[command].str; struct commit *commit; if (prepare_revs(opts)) From 0473f28ad700b9bf4e7dd279f33950fdc968dfa2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:18 +0100 Subject: [PATCH 0183/1540] sequencer (rebase -i): write an author-script file When the interactive rebase aborts, it writes out an author-script file to record the author information for the current commit. As we are about to teach the sequencer how to perform the actions behind an interactive rebase, it needs to write those author-script files, too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 29b944d724a4f9..991388260354f2 100644 --- a/sequencer.c +++ b/sequencer.c @@ -483,6 +483,52 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static int write_author_script(const char *message) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + int res; + + for (;;) + if (!*message || starts_with(message, "\n")) { +missing_author: + /* Missing 'author' line? */ + unlink(rebase_path_author_script()); + return 0; + } else if (skip_prefix(message, "author ", &message)) + break; + else if ((eol = strchr(message, '\n'))) + message = eol + 1; + else + goto missing_author; + + strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, " <", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, "> ", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); + while (*message && *message != '\n' && *message != '\r') + if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); + strbuf_release(&buf); + return res; +} + /* * Read the author-script file into an environment block, ready for use in * run_command(), that can be free()d afterwards. @@ -935,7 +981,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) + res = -1; + else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) From 9d93ccd1d2d2d40a3c7ecc76740caf28683144ac Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:21 +0100 Subject: [PATCH 0184/1540] sequencer (rebase -i): allow continuing with staged changes When an interactive rebase is interrupted, the user may stage changes before continuing, and we need to commit those changes in that case. Please note that the nested "if" added to the sequencer_continue() is not combined into a single "if" because it will be extended with an "else" clause in a later patch in this patch series. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/sequencer.c b/sequencer.c index 991388260354f2..69301fecc6cf7d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1826,6 +1826,42 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } +static int commit_staged_changes(struct replay_opts *opts) +{ + int amend = 0; + + if (has_unstaged_changes(1)) + return error(_("cannot rebase: You have unstaged changes.")); + if (!has_uncommitted_changes(0)) + return 0; + + if (file_exists(rebase_path_amend())) { + struct strbuf rev = STRBUF_INIT; + unsigned char head[20], to_amend[20]; + + if (get_sha1("HEAD", head)) + return error(_("cannot amend non-existing commit")); + if (!read_oneliner(&rev, rebase_path_amend(), 0)) + return error(_("invalid file: '%s'"), rebase_path_amend()); + if (get_sha1_hex(rev.buf, to_amend)) + return error(_("invalid contents: '%s'"), + rebase_path_amend()); + if (hashcmp(head, to_amend)) + return error(_("\nYou have uncommitted changes in your " + "working tree. Please, commit them\n" + "first and then run 'git rebase " + "--continue' again.")); + + strbuf_release(&rev); + amend = 1; + } + + if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0)) + return error(_("could not commit staged changes.")); + unlink(rebase_path_amend()); + return 0; +} + int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -1834,6 +1870,10 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; + if (is_rebase_i(opts)) { + if (commit_staged_changes(opts)) + return -1; + } if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) From 5263220967209c7a64c065054fb64036815ac2ee Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:25 +0100 Subject: [PATCH 0185/1540] sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed The scripted version of the interactive rebase already does that. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 69301fecc6cf7d..52e17c8887ad73 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1832,8 +1832,13 @@ static int commit_staged_changes(struct replay_opts *opts) if (has_unstaged_changes(1)) return error(_("cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) + if (!has_uncommitted_changes(0)) { + const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error(_("could not remove CHERRY_PICK_HEAD")); return 0; + } if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; From 4258a6da90988439af1d6008a3172d25efcbf2a1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:30 +0100 Subject: [PATCH 0186/1540] sequencer (rebase -i): skip some revert/cherry-pick specific code path When a cherry-pick continues without a "todo script", the intention is simply to pick a single commit. However, when an interactive rebase is continued without a "todo script", it means that the last command has been completed and that we now need to clean up. This commit guards the revert/cherry-pick specific steps so that they are not executed in rebase -i mode. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sequencer.c b/sequencer.c index 52e17c8887ad73..a7b9ee0d04295e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1878,26 +1878,28 @@ int sequencer_continue(struct replay_opts *opts) if (is_rebase_i(opts)) { if (commit_staged_changes(opts)) return -1; - } - if (!file_exists(get_todo_path(opts))) + } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) + if (!is_rebase_i(opts)) { + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) + goto release_todo_list; + } + if (index_differs_from("HEAD", 0, 0)) { + res = error_dirty_index(opts); goto release_todo_list; + } + todo_list.current++; } - if (index_differs_from("HEAD", 0, 0)) { - res = error_dirty_index(opts); - goto release_todo_list; - } - todo_list.current++; + res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); From 52865279ee1187a0f630779d2015587ae702b1f4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:34 +0100 Subject: [PATCH 0187/1540] sequencer (rebase -i): the todo can be empty when continuing When the last command of an interactive rebase fails, the user needs to resolve the problem and then continue the interactive rebase. Naturally, the todo script is empty by then. So let's not complain about that! To that end, let's move that test out of the function that parses the todo script, and into the more high-level function read_populate_todo(). This is also necessary by now because the lower-level parse_insn_buffer() has no idea whether we are performing an interactive rebase or not. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index a7b9ee0d04295e..6a840216b16fe5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1215,8 +1215,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) else if (!is_noop(item->command)) fixup_okay = 1; } - if (!todo_list->nr) - return error(_("no commits parsed.")); + return res; } @@ -1240,6 +1239,10 @@ static int read_populate_todo(struct todo_list *todo_list, if (res) return error(_("unusable instruction sheet: '%s'"), todo_file); + if (!todo_list->nr && + (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) + return error(_("no commits parsed.")); + if (!is_rebase_i(opts)) { enum todo_command valid = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; From 4b83ce9f6772451d4582dc1f0891790acbdf4eb1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:53 +0100 Subject: [PATCH 0188/1540] sequencer (rebase -i): update refs after a successful rebase An interactive rebase operates on a detached HEAD (to keep the reflog of the original branch relatively clean), and updates the branch only at the end. Now that the sequencer learns to perform interactive rebases, it also needs to learn the trick to update the branch before removing the directory containing the state of the interactive rebase. We introduce a new head_ref variable in a wider scope than necessary at the moment, to allow for a later patch that prints out "Successfully rebased and updated ". Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 6a840216b16fe5..80b2b2a975b8f8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -102,6 +102,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") +static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1784,12 +1786,53 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { - struct strbuf buf = STRBUF_INIT; + struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && + starts_with(head_ref.buf, "refs/")) { + unsigned char head[20], orig[20]; + int res; + + if (get_sha1("HEAD", head)) { + res = error(_("cannot read HEAD")); +cleanup_head_ref: + strbuf_release(&head_ref); + strbuf_release(&buf); + return res; + } + if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || + get_sha1_hex(buf.buf, orig)) { + res = error(_("could not read orig-head")); + goto cleanup_head_ref; + } + strbuf_addf(&buf, "rebase -i (finish): %s onto ", + head_ref.buf); + if (!read_oneliner(&buf, rebase_path_onto(), 0)) { + res = error(_("could not read 'onto'")); + goto cleanup_head_ref; + } + if (update_ref(buf.buf, head_ref.buf, head, orig, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { + res = error(_("could not update %s"), + head_ref.buf); + goto cleanup_head_ref; + } + strbuf_reset(&buf); + strbuf_addf(&buf, + "rebase -i (finish): returning to %s", + head_ref.buf); + if (create_symref("HEAD", head_ref.buf, buf.buf)) { + res = error(_("could not update HEAD to %s"), + head_ref.buf); + goto cleanup_head_ref; + } + strbuf_reset(&buf); + } + if (opts->verbose) { struct rev_info log_tree_opt; struct object_id orig, head; @@ -1810,6 +1853,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } } strbuf_release(&buf); + strbuf_release(&head_ref); } /* From 4a5146f9d2f739803b8fde643e02c2a9474fb0e8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:27:57 +0100 Subject: [PATCH 0189/1540] sequencer (rebase -i): leave a patch upon error When doing an interactive rebase, we want to leave a 'patch' file for further inspection by the user (even if we never tried to actually apply that patch, since we're cherry-picking instead). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 80b2b2a975b8f8..a2002f1c12e441 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1769,7 +1769,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) intend_to_amend(); return error_failed_squash(item->commit, opts, item->arg_len, item->arg); - } + } else if (res && is_rebase_i(opts)) + return res | error_with_patch(item->commit, + item->arg, item->arg_len, opts, res, 0); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; From 04efc8b57c17ab31de1c4c53e52838659dfeecc5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:00 +0100 Subject: [PATCH 0190/1540] sequencer (rebase -i): implement the 'reword' command This is now trivial, as all the building blocks are in place: all we need to do is to flip the "edit" switch when committing. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index a2002f1c12e441..50e998acc4c574 100644 --- a/sequencer.c +++ b/sequencer.c @@ -718,6 +718,7 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_REWORD, TODO_FIXUP, TODO_SQUASH, /* commands that do something else than handling a single commit */ @@ -733,6 +734,7 @@ static struct { { 'p', "pick" }, { 0, "revert" }, { 'e', "edit" }, + { 'r', "reword" }, { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, @@ -962,7 +964,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (is_fixup(command)) { + if (command == TODO_REWORD) + edit = 1; + else if (is_fixup(command)) { if (update_squash_messages(command, commit, opts)) return -1; amend = 1; @@ -1771,7 +1775,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg_len, item->arg); } else if (res && is_rebase_i(opts)) return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, res, 0); + item->arg, item->arg_len, opts, res, + item->command == TODO_REWORD); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; From bcbb68be2e287209eb257de314d72d022712e4c9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:05 +0100 Subject: [PATCH 0191/1540] sequencer (rebase -i): allow fast-forwarding for edit/reword The sequencer already knew how to fast-forward instead of cherry-picking, if possible. We want to continue to do this, of course, but in case of the 'reword' command, we will need to call `git commit` after fast-forwarding. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index 50e998acc4c574..23161f593e2ce9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -860,7 +860,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, amend = 0, allow; + int res, unborn = 0, amend = 0, allow = 0; if (opts->no_commit) { /* @@ -905,11 +905,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; + if (get_message(commit, &msg) != 0) + return error(_("cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) - return fast_forward_to(commit->object.oid.hash, head, unborn, opts); - + (!parent && unborn))) { + if (is_rebase_i(opts)) + write_author_script(msg.message); + res = fast_forward_to(commit->object.oid.hash, head, unborn, + opts); + if (res || command != TODO_REWORD) + goto leave; + edit = amend = 1; + msg_file = NULL; + goto fast_forward_edit; + } if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be a "todo" command like "revert" or "pick", the second %s a SHA1. */ @@ -917,10 +929,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, command_to_string(command), oid_to_hex(&parent->object.oid)); - if (get_message(commit, &msg) != 0) - return error(_("cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -1044,6 +1052,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) +fast_forward_edit: res = run_git_commit(msg_file, opts, allow, edit, amend, cleanup_commit_message); From 96e832a5fd6a97f8d40d89d4c1e97e7e3534edf0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:09 +0100 Subject: [PATCH 0192/1540] sequencer (rebase -i): refactor setting the reflog message This makes the code DRYer, with the obvious benefit that we can enhance the code further in a single place. We can also reuse the functionality elsewhere by calling this new function. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 23161f593e2ce9..0d8e11f5800ed9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1743,6 +1743,26 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1810,6 +1830,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && starts_with(head_ref.buf, "refs/")) { + const char *msg; unsigned char head[20], orig[20]; int res; @@ -1825,23 +1846,21 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) res = error(_("could not read orig-head")); goto cleanup_head_ref; } - strbuf_addf(&buf, "rebase -i (finish): %s onto ", - head_ref.buf); if (!read_oneliner(&buf, rebase_path_onto(), 0)) { res = error(_("could not read 'onto'")); goto cleanup_head_ref; } - if (update_ref(buf.buf, head_ref.buf, head, orig, + msg = reflog_message(opts, "finish", "%s onto %s", + head_ref.buf, buf.buf); + if (update_ref(msg, head_ref.buf, head, orig, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { res = error(_("could not update %s"), head_ref.buf); goto cleanup_head_ref; } - strbuf_reset(&buf); - strbuf_addf(&buf, - "rebase -i (finish): returning to %s", + msg = reflog_message(opts, "finish", "returning to %s", head_ref.buf); - if (create_symref("HEAD", head_ref.buf, buf.buf)) { + if (create_symref("HEAD", head_ref.buf, msg)) { res = error(_("could not update HEAD to %s"), head_ref.buf); goto cleanup_head_ref; From 8ab37ef21f9c48bf034128523c489b4f39bcbb88 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:13 +0100 Subject: [PATCH 0193/1540] sequencer (rebase -i): set the reflog message consistently We already used the same reflog message as the scripted version of rebase -i when finishing. With this commit, we do that also for all the commands before that. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index 0d8e11f5800ed9..95ae4bcd1e90ca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1785,6 +1785,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_amend()); } if (item->command <= TODO_SQUASH) { + if (is_rebase_i(opts)) + setenv("GIT_REFLOG_ACTION", reflog_message(opts, + command_to_string(item->command), NULL), + 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { From 25cb8df97c9be26d7638e79595d361fbc40b65a0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:16 +0100 Subject: [PATCH 0194/1540] sequencer (rebase -i): copy commit notes at end When rebasing commits that have commit notes attached, the interactive rebase rewrites those notes faithfully at the end. The sequencer must do this, too, if it wishes to do interactive rebase's job. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/sequencer.c b/sequencer.c index 95ae4bcd1e90ca..50380a15b83d7d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -95,6 +95,15 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") * the abbreviated commit name of the corresponding patch. */ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") +/* + * For the post-rewrite hook, we make a list of rewritten commits and + * their new sha1s. The rewritten-pending list keeps the sha1s of + * commits that have been processed, but not committed yet, + * e.g. because they are waiting for a 'squash' command. + */ +static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") +static GIT_PATH_FUNC(rebase_path_rewritten_pending, + "rebase-merge/rewritten-pending") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -850,6 +859,44 @@ static int update_squash_messages(enum todo_command command, return res; } +static void flush_rewritten_pending(void) { + struct strbuf buf = STRBUF_INIT; + unsigned char newsha1[20]; + FILE *out; + + if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && + !get_sha1("HEAD", newsha1) && + (out = fopen(rebase_path_rewritten_list(), "a"))) { + char *bol = buf.buf, *eol; + + while (*bol) { + eol = strchrnul(bol, '\n'); + fprintf(out, "%.*s %s\n", (int)(eol - bol), + bol, sha1_to_hex(newsha1)); + if (!*eol) + break; + bol = eol + 1; + } + fclose(out); + unlink(rebase_path_rewritten_pending()); + } + strbuf_release(&buf); +} + +static void record_in_rewritten(struct object_id *oid, + enum todo_command next_command) { + FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + + if (!out) + return; + + fprintf(out, "%s\n", oid_to_hex(oid)); + fclose(out); + + if (!is_fixup(next_command)) + flush_rewritten_pending(); +} + static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts, int final_fixup) { @@ -1743,6 +1790,17 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static enum todo_command peek_command(struct todo_list *todo_list, int offset) +{ + int i; + + for (i = todo_list->current + offset; i < todo_list->nr; i++) + if (!is_noop(todo_list->items[i].command)) + return todo_list->items[i].command; + + return -1; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1801,6 +1859,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); if (res && is_fixup(item->command)) { if (res == 1) intend_to_amend(); @@ -1827,6 +1888,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (is_rebase_i(opts)) { struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; + struct stat st; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) @@ -1891,6 +1953,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) log_tree_diff_flush(&log_tree_opt); } } + flush_rewritten_pending(); + if (!stat(rebase_path_rewritten_list(), &st) && + st.st_size > 0) { + struct child_process child = CHILD_PROCESS_INIT; + + child.in = open(rebase_path_rewritten_list(), O_RDONLY); + child.git_cmd = 1; + argv_array_push(&child.args, "notes"); + argv_array_push(&child.args, "copy"); + argv_array_push(&child.args, "--for-rewrite=rebase"); + /* we don't care if this copying failed */ + run_command(&child); + } + strbuf_release(&buf); strbuf_release(&head_ref); } From ca98c6d4870d9743ee1fab9f70d1f455895811d6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:20 +0100 Subject: [PATCH 0195/1540] sequencer (rebase -i): record interrupted commits in rewritten, too When continuing after a `pick` command failed, we want that commit to show up in the rewritten-list (and its notes to be rewritten), too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sequencer.c b/sequencer.c index 50380a15b83d7d..d7273fd1b3d850 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2060,6 +2060,14 @@ int sequencer_continue(struct replay_opts *opts) goto release_todo_list; } todo_list.current++; + } else if (file_exists(rebase_path_stopped_sha())) { + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) && + !get_sha1_committish(buf.buf, oid.hash)) + record_in_rewritten(&oid, peek_command(&todo_list, 0)); + strbuf_release(&buf); } res = pick_commits(&todo_list, opts); From 795160457db0982c44cc02f66798a35af4d15625 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:23 +0100 Subject: [PATCH 0196/1540] sequencer (rebase -i): run the post-rewrite hook, if needed Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sequencer.c b/sequencer.c index d7273fd1b3d850..43ced8db31adbc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1957,6 +1957,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process child = CHILD_PROCESS_INIT; + const char *post_rewrite_hook = + find_hook("post-rewrite"); child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.git_cmd = 1; @@ -1965,6 +1967,18 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) argv_array_push(&child.args, "--for-rewrite=rebase"); /* we don't care if this copying failed */ run_command(&child); + + if (post_rewrite_hook) { + struct child_process hook = CHILD_PROCESS_INIT; + + hook.in = open(rebase_path_rewritten_list(), + O_RDONLY); + hook.stdout_to_stderr = 1; + argv_array_push(&hook.args, post_rewrite_hook); + argv_array_push(&hook.args, "rebase"); + /* we don't care if this hook failed */ + run_command(&hook); + } } strbuf_release(&buf); From 796c7972c7236634655d2333333e9f871149b994 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:27 +0100 Subject: [PATCH 0197/1540] sequencer (rebase -i): respect the rebase.autostash setting Git's `rebase` command inspects the `rebase.autostash` config setting to determine whether it should stash any uncommitted changes before rebasing and re-apply them afterwards. As we introduce more bits and pieces to let the sequencer act as interactive rebase's backend, here is the part that adds support for the autostash feature. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sequencer.c b/sequencer.c index 43ced8db31adbc..06f7cebe488d6e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -113,6 +113,7 @@ static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") +static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1801,6 +1802,47 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } +static int apply_autostash(struct replay_opts *opts) +{ + struct strbuf stash_sha1 = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; + + if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { + strbuf_release(&stash_sha1); + return 0; + } + strbuf_trim(&stash_sha1); + + child.git_cmd = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_sha1.buf); + if (!run_command(&child)) + printf(_("Applied autostash.")); + else { + struct child_process store = CHILD_PROCESS_INIT; + + store.git_cmd = 1; + argv_array_push(&store.args, "stash"); + argv_array_push(&store.args, "store"); + argv_array_push(&store.args, "-m"); + argv_array_push(&store.args, "autostash"); + argv_array_push(&store.args, "-q"); + argv_array_push(&store.args, stash_sha1.buf); + if (run_command(&store)) + ret = error(_("cannot store %s"), stash_sha1.buf); + else + printf(_("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" + " \"git stash drop\" at any time.\n")); + } + + strbuf_release(&stash_sha1); + return ret; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1980,6 +2022,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) run_command(&hook); } } + apply_autostash(opts); strbuf_release(&buf); strbuf_release(&head_ref); From ca6c6b45dd9c36ae3e0b4f1fc9812d1e6a8b59bc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:30 +0100 Subject: [PATCH 0198/1540] sequencer (rebase -i): respect strategy/strategy_opts settings The sequencer already has an idea about using different merge strategies. We just piggy-back on top of that, using rebase -i's own settings, when running the sequencer in interactive rebase mode. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 06f7cebe488d6e..04a64cf0dc00e2 100644 --- a/sequencer.c +++ b/sequencer.c @@ -114,6 +114,8 @@ static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") +static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") +static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1368,6 +1370,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } +static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) +{ + int i; + + strbuf_reset(buf); + if (!read_oneliner(buf, rebase_path_strategy(), 0)) + return; + opts->strategy = strbuf_detach(buf, NULL); + if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) + return; + + opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts); + for (i = 0; i < opts->xopts_nr; i++) { + const char *arg = opts->xopts[i]; + + skip_prefix(arg, "--", &arg); + opts->xopts[i] = xstrdup(arg); + } +} + static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { @@ -1381,11 +1403,13 @@ static int read_populate_opts(struct replay_opts *opts) opts->gpg_sign = xstrdup(buf.buf + 2); } } - strbuf_release(&buf); if (file_exists(rebase_path_verbose())) opts->verbose = 1; + read_strategy_opts(opts, &buf); + strbuf_release(&buf); + return 0; } From 9d7bf3cf993ab59e0f9d7150534213f57dd12741 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:28:34 +0100 Subject: [PATCH 0199/1540] sequencer (rebase -i): allow rescheduling commands The interactive rebase has the very special magic that a cherry-pick that exits with a status different from 0 and 1 signifies a failure to even record that a cherry-pick was started. This can happen e.g. when a fast-forward fails because it would overwrite untracked files. In that case, we must reschedule the command that we thought we already had at least started successfully. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sequencer.c b/sequencer.c index 04a64cf0dc00e2..dd5b843a841f6c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1915,6 +1915,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + } if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) From b3fdd581ae0e8793b8a1dabbee19334067229888 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:34:34 +0100 Subject: [PATCH 0200/1540] sequencer (rebase -i): implement the 'drop' command The parsing part of a 'drop' command is almost identical to parsing a 'pick', while the operation is the same as that of a 'noop'. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index dd5b843a841f6c..6e92f186ae3a44 100644 --- a/sequencer.c +++ b/sequencer.c @@ -736,7 +736,8 @@ enum todo_command { /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP + TODO_NOOP, + TODO_DROP }; static struct { @@ -750,7 +751,8 @@ static struct { { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, - { 0, "noop" } + { 0, "noop" }, + { 'd', "drop" } }; static const char *command_to_string(const enum todo_command command) @@ -762,7 +764,7 @@ static const char *command_to_string(const enum todo_command command) static int is_noop(const enum todo_command command) { - return TODO_NOOP <= (size_t)command; + return TODO_NOOP <= command; } static int is_fixup(enum todo_command command) From ac191470c7dba893388f4038851d68fea2094cea Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 16:34:39 +0100 Subject: [PATCH 0201/1540] sequencer (rebase -i): differentiate between comments and 'noop' In the upcoming patch, we will support rebase -i's progress reporting. The progress skips comments but counts 'noop's. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sequencer.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 6e92f186ae3a44..41f80ea2c42095 100644 --- a/sequencer.c +++ b/sequencer.c @@ -737,7 +737,9 @@ enum todo_command { TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP, - TODO_DROP + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; static struct { @@ -752,12 +754,13 @@ static struct { { 's', "squash" }, { 'x', "exec" }, { 0, "noop" }, - { 'd', "drop" } + { 'd', "drop" }, + { 0, NULL } }; static const char *command_to_string(const enum todo_command command) { - if ((size_t)command < ARRAY_SIZE(todo_command_info)) + if (command < TODO_COMMENT) return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1198,14 +1201,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) bol += strspn(bol, " \t"); if (bol == eol || *bol == '\r' || *bol == comment_line_char) { - item->command = TODO_NOOP; + item->command = TODO_COMMENT; item->commit = NULL; item->arg = bol; item->arg_len = eol - bol; return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + for (i = 0; i < TODO_COMMENT; i++) if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; @@ -1214,7 +1217,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_info)) + if (i >= TODO_COMMENT) return -1; if (item->command == TODO_NOOP) { From 2d81c48fa7f7679a92c9fe674b53656166ade4f8 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 15:16:50 -0800 Subject: [PATCH 0202/1540] pathspec: give better message for submodule related pathspec error Every once in a while someone complains to the mailing list to have run into this weird assertion[1]. The usual response from the mailing list is link to old discussions[2], and acknowledging the problem stating it is known. This patch accomplishes two things: 1. Switch assert() to die("BUG") to give a more readable message. 2. Take one of the cases where we hit a BUG and turn it into a normal "there was something wrong with the input" message. This assertion triggered for cases where there wasn't a programming bug, but just bogus input. In particular, if the user asks for a pathspec that is inside a submodule, we shouldn't assert() or die("BUG"); we should tell the user their request is bogus. The only reason we did not check for it, is the expensive nature of such a check, so callers avoid setting the flag PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE. However when we die due to bogus input, the expense of CPU cycles spent outweighs the user wondering what went wrong, so run that check unconditionally before dying with a more generic error message. Note: There is a case (e.g. "git -C submodule add .") in which we call strip_submodule_slash_expensive, as git-add requests it via the flag PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE, but the assert used to trigger nevertheless, because the flag PATHSPEC_LITERAL was not set, such that we executed if (item->nowildcard_len < prefixlen) item->nowildcard_len = prefixlen; and prefixlen was not adapted (e.g. it was computed from "submodule/") So in the die_inside_submodule_path function we also need handle paths, that were stripped before, i.e. are the exact submodule path. This is why the conditions in die_inside_submodule_path are slightly different than in strip_submodule_slash_expensive. [1] https://www.google.com/search?q=item-%3Enowildcard_len [2] http://git.661346.n2.nabble.com/assert-failed-in-submodule-edge-case-td7628687.html https://www.spinics.net/lists/git/msg249473.html Helped-by: Jeff King Helped-by: Junio C Hamano Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- pathspec.c | 35 +++++++++++++++++++++++++++++-- t/t6134-pathspec-in-submodule.sh | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100755 t/t6134-pathspec-in-submodule.sh diff --git a/pathspec.c b/pathspec.c index ff2509ddd1519b..7ababb315944d5 100644 --- a/pathspec.c +++ b/pathspec.c @@ -296,6 +296,27 @@ static void strip_submodule_slash_expensive(struct pathspec_item *item) } } +static void die_inside_submodule_path(struct pathspec_item *item) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + if (item->len < ce_len || + !(item->match[ce_len] == '/' || item->match[ce_len] == '\0') || + memcmp(ce->name, item->match, ce_len)) + continue; + + die(_("Pathspec '%s' is in submodule '%.*s'"), + item->original, ce_len, ce->name); + } +} + /* * Perform the initialization of a pathspec_item based on a pathspec element. */ @@ -391,8 +412,18 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, } /* sanity checks, pathspec matchers assume these are sane */ - assert(item->nowildcard_len <= item->len && - item->prefix <= item->len); + if (item->nowildcard_len > item->len || + item->prefix > item->len) { + /* + * This case can be triggered by the user pointing us to a + * pathspec inside a submodule, which is an input error. + * Detect that here and complain, but fallback in the + * non-submodule case to a BUG, as we have no idea what + * would trigger that. + */ + die_inside_submodule_path(item); + die ("BUG: item->nowildcard_len > item->len || item->prefix > item->len)"); + } } static int pathspec_item_cmp(const void *a_, const void *b_) diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh new file mode 100755 index 00000000000000..fd401ca605681a --- /dev/null +++ b/t/t6134-pathspec-in-submodule.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='test case exclude pathspec' + +. ./test-lib.sh + +test_expect_success 'setup a submodule' ' + test_create_repo pretzel && + : >pretzel/a && + git -C pretzel add a && + git -C pretzel commit -m "add a file" -- a && + git submodule add ./pretzel sub && + git commit -a -m "add submodule" && + git submodule deinit --all +' + +cat <expect +fatal: Pathspec 'sub/a' is in submodule 'sub' +EOF + +test_expect_success 'error message for path inside submodule' ' + echo a >sub/a && + test_must_fail git add sub/a 2>actual && + test_cmp expect actual +' + +cat <expect +fatal: Pathspec '.' is in submodule 'sub' +EOF + +test_expect_success 'error message for path inside submodule from within submodule' ' + test_must_fail git -C sub add . 2>actual && + test_cmp expect actual +' + +test_done From 314caebe214928e4087d67418f837e58c8294586 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 9 Jan 2017 18:29:28 -0500 Subject: [PATCH 0203/1540] .mailmap: record canonical email for Richard Hansen When I changed employers my work address changed from rhansen@bbn.com to hansenr@google.com. Rather than map my old work address to my new, map them both to my permanent personal email address. (I will still use my work address in commits I submit so that my employer gets some credit.) Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 9cc33e925de8ad..9c87a3840b240e 100644 --- a/.mailmap +++ b/.mailmap @@ -192,6 +192,8 @@ Philippe Bruhat Ralf Thielow Ramsay Jones René Scharfe +Richard Hansen +Richard Hansen Robert Fitzsimons Robert Shearman Robert Zeh From 30ac275b1c893697e25abefbd872de534bb8c046 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 11:46:17 -0800 Subject: [PATCH 0204/1540] unpack-trees: move checkout state into check_updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checkout state was introduced via 16da134b1f9 (read-trees: refactor the unpack_trees() part, 2006-07-30). An attempt to refactor the checkout state was done in b56aa5b268e (unpack-trees: pass checkout state explicitly to check_updates(), 2016-09-13), but we can go even further. The `struct checkout state` is not used in unpack_trees apart from initializing it, so move it into the function that makes use of it, which is `check_updates`. Signed-off-by: Stefan Beller Reviewed-by: René Scharfe Signed-off-by: Junio C Hamano --- unpack-trees.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index ea6bdd20e04915..b6f0bc6d4cf609 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -218,14 +218,19 @@ static void unlink_entry(const struct cache_entry *ce) schedule_dir_for_removal(ce->name, ce_namelen(ce)); } -static int check_updates(struct unpack_trees_options *o, - const struct checkout *state) +static int check_updates(struct unpack_trees_options *o) { unsigned cnt = 0, total = 0; + int errs = 0; struct progress *progress = NULL; struct index_state *index = &o->result; + struct checkout state = CHECKOUT_INIT; int i; - int errs = 0; + + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + state.istate = index; if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { @@ -240,7 +245,7 @@ static int check_updates(struct unpack_trees_options *o, } if (o->update) - git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result); + git_attr_set_direction(GIT_ATTR_CHECKOUT, index); for (i = 0; i < index->cache_nr; i++) { const struct cache_entry *ce = index->cache[i]; @@ -251,7 +256,7 @@ static int check_updates(struct unpack_trees_options *o, continue; } } - remove_marked_cache_entries(&o->result); + remove_marked_cache_entries(index); remove_scheduled_dirs(); for (i = 0; i < index->cache_nr; i++) { @@ -264,7 +269,7 @@ static int check_updates(struct unpack_trees_options *o, display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; if (o->update && !o->dry_run) { - errs |= checkout_entry(ce, state, NULL); + errs |= checkout_entry(ce, &state, NULL); } } } @@ -1094,14 +1099,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options int i, ret; static struct cache_entry *dfc; struct exclude_list el; - struct checkout state = CHECKOUT_INIT; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); - state.force = 1; - state.quiet = 1; - state.refresh_cache = 1; - state.istate = &o->result; memset(&el, 0, sizeof(el)); if (!core_apply_sparse_checkout || !o->update) @@ -1238,7 +1238,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } o->src_index = NULL; - ret = check_updates(o, &state) ? (-2) : 0; + ret = check_updates(o) ? (-2) : 0; if (o->dst_index) { if (!ret) { if (!o->result.cache_tree) From c4bfc7728b32294c33661c16a861419290549a2b Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 11:46:18 -0800 Subject: [PATCH 0205/1540] unpack-trees: remove unneeded continue The continue is the last statement in the loop, so not needed. This situation arose in 700e66d66 (2010-07-30, unpack-trees: let read-tree -u remove index entries outside sparse area) when statements after the continue were removed. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- unpack-trees.c | 1 - 1 file changed, 1 deletion(-) diff --git a/unpack-trees.c b/unpack-trees.c index b6f0bc6d4cf609..9e48a4048d0f5c 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -253,7 +253,6 @@ static int check_updates(struct unpack_trees_options *o) display_progress(progress, ++cnt); if (o->update && !o->dry_run) unlink_entry(ce); - continue; } } remove_marked_cache_entries(index); From 384f1a167bdf0b68f90ec54e9845c1b4a68cb670 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 11:46:19 -0800 Subject: [PATCH 0206/1540] unpack-trees: factor progress setup out of check_updates This makes check_updates shorter and easier to understand. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- unpack-trees.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 9e48a4048d0f5c..b90c5407267c42 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -218,9 +218,27 @@ static void unlink_entry(const struct cache_entry *ce) schedule_dir_for_removal(ce->name, ce_namelen(ce)); } -static int check_updates(struct unpack_trees_options *o) +static struct progress *get_progress(struct unpack_trees_options *o) { unsigned cnt = 0, total = 0; + struct index_state *index = &o->result; + + if (!o->update || !o->verbose_update) + return NULL; + + for (; cnt < index->cache_nr; cnt++) { + const struct cache_entry *ce = index->cache[cnt]; + if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE)) + total++; + } + + return start_progress_delay(_("Checking out files"), + total, 50, 1); +} + +static int check_updates(struct unpack_trees_options *o) +{ + unsigned cnt = 0; int errs = 0; struct progress *progress = NULL; struct index_state *index = &o->result; @@ -232,17 +250,7 @@ static int check_updates(struct unpack_trees_options *o) state.refresh_cache = 1; state.istate = index; - if (o->update && o->verbose_update) { - for (total = cnt = 0; cnt < index->cache_nr; cnt++) { - const struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE)) - total++; - } - - progress = start_progress_delay(_("Checking out files"), - total, 50, 1); - cnt = 0; - } + progress = get_progress(o); if (o->update) git_attr_set_direction(GIT_ATTR_CHECKOUT, index); From 11873b438faa13ff1416741bb48dbdaa8a6cc083 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:50 -0500 Subject: [PATCH 0207/1540] rev-parse doc: pass "--" to rev-parse in the --prefix example The "--" argument avoids "ambiguous argument: unknown revision or path not in the working tree" errors when a pathname argument refers to a non-existent file. The "--" passed explicitly to set was removed because rev-parse outputs the "--" argument that it is given. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index b6c6326cdc7bb4..7241e968935505 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -91,7 +91,8 @@ repository. For example: ---- prefix=$(git rev-parse --show-prefix) cd "$(git rev-parse --show-toplevel)" -eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")" +# rev-parse provides the -- needed for 'set' +eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")" ---- --verify:: From ecfdf0bd2fe4edb2835e81b09f8df26cd7d3f437 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:51 -0500 Subject: [PATCH 0208/1540] t7610: update branch names to match test number Rename the testNN branches so that NN matches the test number. This should make it easier to troubleshoot test issues. Use $test_count to keep this future-proof. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 82 ++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 63d36fb28e4442..780d287ef5fa15 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -94,7 +94,7 @@ test_expect_success 'setup' ' ' test_expect_success 'custom mergetool' ' - git checkout -b test1 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -113,7 +113,7 @@ test_expect_success 'custom mergetool' ' test_expect_success 'mergetool crlf' ' test_config core.autocrlf true && - git checkout -b test2 branch1 && + git checkout -b test$test_count branch1 && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && @@ -134,7 +134,7 @@ test_expect_success 'mergetool crlf' ' ' test_expect_success 'mergetool in subdir' ' - git checkout -b test3 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -161,7 +161,7 @@ test_expect_success 'mergetool on file in parent dir' ' ' test_expect_success 'mergetool skips autoresolved' ' - git checkout -b test4 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && @@ -192,7 +192,7 @@ test_expect_success 'mergetool merges all from subdir' ' test_expect_success 'mergetool skips resolved paths when rerere is active' ' test_config rerere.enabled true && rm -rf .git/rr-cache && - git checkout -b test5 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) && @@ -233,7 +233,7 @@ test_expect_success 'conflicted stash sets up rerere' ' test_expect_success 'mergetool takes partial path' ' git reset --hard && test_config rerere.enabled false && - git checkout -b test12 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && @@ -308,12 +308,12 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' ' test_expect_success 'deleted vs modified submodule' ' - git checkout -b test6 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && git rm --cached submod && git commit -m "Submodule deleted from branch" && - git checkout -b test6.a test6 && + git checkout -b test$test_count.a test$test_count && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -329,7 +329,7 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by keeping module" && mv submod submod-movedaside && - git checkout -b test6.b test6 && + git checkout -b test$test_count.b test$test_count && git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && @@ -343,9 +343,9 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by deleting module" && mv submod-movedaside submod && - git checkout -b test6.c master && + git checkout -b test$test_count.c master && git submodule update -N && - test_must_fail git merge test6 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -359,9 +359,9 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by deleting module" && mv submod.orig submod && - git checkout -b test6.d master && + git checkout -b test$test_count.d master && git submodule update -N && - test_must_fail git merge test6 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -377,14 +377,14 @@ test_expect_success 'deleted vs modified submodule' ' ' test_expect_success 'file vs modified submodule' ' - git checkout -b test7 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && git rm --cached submod && echo not a submodule >submod && git add submod && git commit -m "Submodule path becomes file" && - git checkout -b test7.a branch1 && + git checkout -b test$test_count.a branch1 && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -400,7 +400,7 @@ test_expect_success 'file vs modified submodule' ' git commit -m "Merge resolved by keeping module" && mv submod submod-movedaside && - git checkout -b test7.b test7 && + git checkout -b test$test_count.b test$test_count && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -413,11 +413,11 @@ test_expect_success 'file vs modified submodule' ' test "$output" = "No files need merging" && git commit -m "Merge resolved by keeping file" && - git checkout -b test7.c master && + git checkout -b test$test_count.c master && rmdir submod && mv submod-movedaside submod && test ! -e submod.orig && git submodule update -N && - test_must_fail git merge test7 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -430,10 +430,10 @@ test_expect_success 'file vs modified submodule' ' test "$output" = "No files need merging" && git commit -m "Merge resolved by keeping file" && - git checkout -b test7.d master && + git checkout -b test$test_count.d master && rmdir submod && mv submod.orig submod && git submodule update -N && - test_must_fail git merge test7 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both>/dev/null 2>&1 ) && @@ -448,7 +448,7 @@ test_expect_success 'file vs modified submodule' ' ' test_expect_success 'submodule in subdirectory' ' - git checkout -b test10 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -464,52 +464,52 @@ test_expect_success 'submodule in subdirectory' ' git add subdir/subdir_module && git commit -m "add submodule in subdirectory" && - git checkout -b test10.a test10 && + git checkout -b test$test_count.a test$test_count && git submodule update -N && ( cd subdir/subdir_module && git checkout -b super10.a && - echo test10.a >file15 && + echo test$test_count.a >file15 && git add file15 && git commit -m "on branch 10.a" ) && git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.a" && + git commit -m "change submodule in subdirectory on test$test_count.a" && - git checkout -b test10.b test10 && + git checkout -b test$test_count.b test$test_count && git submodule update -N && ( cd subdir/subdir_module && git checkout -b super10.b && - echo test10.b >file15 && + echo test$test_count.b >file15 && git add file15 && git commit -m "on branch 10.b" ) && git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.b" && + git commit -m "change submodule in subdirectory on test$test_count.b" && - test_must_fail git merge test10.a >/dev/null 2>&1 && + test_must_fail git merge test$test_count.a >/dev/null 2>&1 && ( cd subdir && ( yes "l" | git mergetool subdir_module ) ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git reset --hard && git submodule update -N && - test_must_fail git merge test10.a >/dev/null 2>&1 && + test_must_fail git merge test$test_count.a >/dev/null 2>&1 && ( yes "r" | git mergetool subdir/subdir_module ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.a" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.a" && git commit -m "branch1 resolved with mergetool" && rm -rf subdir/subdir_module ' test_expect_success 'directory vs modified submodule' ' - git checkout -b test11 branch1 && + git checkout -b test$test_count branch1 && mv submod submod-movedaside && git rm --cached submod && mkdir submod && @@ -537,9 +537,9 @@ test_expect_success 'directory vs modified submodule' ' test "$(cat submod/bar)" = "master submodule" && git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && - git checkout -b test11.c master && + git checkout -b test$test_count.c master && git submodule update -N && - test_must_fail git merge test11 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "l" | git mergetool submod ) && git submodule update -N && @@ -547,7 +547,7 @@ test_expect_success 'directory vs modified submodule' ' git reset --hard >/dev/null 2>&1 && git submodule update -N && - test_must_fail git merge test11 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && test ! -e submod.orig && ( yes "r" | git mergetool submod ) && @@ -559,7 +559,7 @@ test_expect_success 'directory vs modified submodule' ' ' test_expect_success 'file with no base' ' - git checkout -b test13 branch1 && + git checkout -b test$test_count branch1 && test_must_fail git merge master && git mergetool --no-prompt --tool mybase -- both && >expected && @@ -568,7 +568,7 @@ test_expect_success 'file with no base' ' ' test_expect_success 'custom commands override built-ins' ' - git checkout -b test14 branch1 && + git checkout -b test$test_count branch1 && test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && test_config mergetool.defaults.trustExitCode true && test_must_fail git merge master && @@ -579,7 +579,7 @@ test_expect_success 'custom commands override built-ins' ' ' test_expect_success 'filenames seen by tools start with ./' ' - git checkout -b test15 branch1 && + git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp false && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && @@ -596,7 +596,7 @@ test_lazy_prereq MKTEMP ' ' test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' - git checkout -b test16 branch1 && + git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp true && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && From 157acfcf353ead1cbd0fff1d9f26ff0b4f1020bc Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:52 -0500 Subject: [PATCH 0209/1540] t7610: move setup code to the 'setup' test case Multiple test cases depend on these hunks, so move them to the 'setup' test case. This is a step toward making the tests more independent so that if one test fails it doesn't cause subsequent tests to fail. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 65 ++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 780d287ef5fa15..6a284814bc9b72 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -55,6 +55,22 @@ test_expect_success 'setup' ' git rm file12 && git commit -m "branch1 changes" && + git checkout -b delete-base branch1 && + mkdir -p a/a && + (echo one; echo two; echo 3; echo 4) >a/a/file.txt && + git add a/a/file.txt && + git commit -m"base file" && + git checkout -b move-to-b delete-base && + mkdir -p b/b && + git mv a/a/file.txt b/b/file.txt && + (echo one; echo two; echo 4) >b/b/file.txt && + git commit -a -m"move to b" && + git checkout -b move-to-c delete-base && + mkdir -p c/c && + git mv a/a/file.txt c/c/file.txt && + (echo one; echo two; echo 3) >c/c/file.txt && + git commit -a -m"move to c" && + git checkout -b stash1 master && echo stash1 change file11 >file11 && git add file11 && @@ -86,6 +102,23 @@ test_expect_success 'setup' ' git rm file11 && git commit -m "master updates" && + git clean -fdx && + git checkout -b order-file-start master && + echo start >a && + echo start >b && + git add a b && + git commit -m start && + git checkout -b order-file-side1 order-file-start && + echo side1 >a && + echo side1 >b && + git add a b && + git commit -m side1 && + git checkout -b order-file-side2 order-file-start && + echo side2 >a && + echo side2 >b && + git add a b && + git commit -m side2 && + git config merge.tool mytool && git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && git config mergetool.mytool.trustExitCode true && @@ -244,21 +277,7 @@ test_expect_success 'mergetool takes partial path' ' ' test_expect_success 'mergetool delete/delete conflict' ' - git checkout -b delete-base branch1 && - mkdir -p a/a && - (echo one; echo two; echo 3; echo 4) >a/a/file.txt && - git add a/a/file.txt && - git commit -m"base file" && - git checkout -b move-to-b delete-base && - mkdir -p b/b && - git mv a/a/file.txt b/b/file.txt && - (echo one; echo two; echo 4) >b/b/file.txt && - git commit -a -m"move to b" && - git checkout -b move-to-c delete-base && - mkdir -p c/c && - git mv a/a/file.txt c/c/file.txt && - (echo one; echo two; echo 3) >c/c/file.txt && - git commit -a -m"move to c" && + git checkout move-to-c && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && ! test -f a/a/file.txt && @@ -608,26 +627,12 @@ test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToT ' test_expect_success 'diff.orderFile configuration is honored' ' + git checkout order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && echo b >order-file && echo a >>order-file && - git checkout -b order-file-start master && - echo start >a && - echo start >b && - git add a b && - git commit -m start && - git checkout -b order-file-side1 order-file-start && - echo side1 >a && - echo side1 >b && - git add a b && - git commit -m side1 && - git checkout -b order-file-side2 order-file-start && - echo side2 >a && - echo side2 >b && - git add a b && - git commit -m side2 && test_must_fail git merge order-file-side1 && cat >expect <<-\EOF && Merging: From 614eb27f0212a875dd6a8ea057ecb4af80d6111d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:53 -0500 Subject: [PATCH 0210/1540] t7610: use test_when_finished for cleanup tasks This is a step toward making the tests more independent so that if one test fails it doesn't cause subsequent tests to fail. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 71 +++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 6a284814bc9b72..ca99c4b1adfabb 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -145,6 +145,11 @@ test_expect_success 'custom mergetool' ' ' test_expect_success 'mergetool crlf' ' + test_when_finished "git reset --hard" && + # This test_config line must go after the above reset line so that + # core.autocrlf is unconfigured before reset runs. (The + # test_config command uses test_when_finished internally and + # test_when_finished is LIFO.) test_config core.autocrlf true && git checkout -b test$test_count branch1 && test_must_fail git merge master >/dev/null 2>&1 && @@ -161,9 +166,7 @@ test_expect_success 'mergetool crlf' ' test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git commit -m "branch1 resolved with mergetool - autocrlf" && - test_config core.autocrlf false && - git reset --hard + git commit -m "branch1 resolved with mergetool - autocrlf" ' test_expect_success 'mergetool in subdir' ' @@ -194,6 +197,7 @@ test_expect_success 'mergetool on file in parent dir' ' ' test_expect_success 'mergetool skips autoresolved' ' + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && @@ -202,8 +206,7 @@ test_expect_success 'mergetool skips autoresolved' ' ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test "$output" = "No files need merging" ' test_expect_success 'mergetool merges all from subdir' ' @@ -223,6 +226,7 @@ test_expect_success 'mergetool merges all from subdir' ' ' test_expect_success 'mergetool skips resolved paths when rerere is active' ' + test_when_finished "git reset --hard" && test_config rerere.enabled true && rm -rf .git/rr-cache && git checkout -b test$test_count branch1 && @@ -232,8 +236,7 @@ test_expect_success 'mergetool skips resolved paths when rerere is active' ' ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) && git submodule update -N && output="$(yes "n" | git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test "$output" = "No files need merging" ' test_expect_success 'conflicted stash sets up rerere' ' @@ -264,6 +267,7 @@ test_expect_success 'conflicted stash sets up rerere' ' ' test_expect_success 'mergetool takes partial path' ' + test_when_finished "git reset --hard" && git reset --hard && test_config rerere.enabled false && git checkout -b test$test_count branch1 && @@ -272,11 +276,11 @@ test_expect_success 'mergetool takes partial path' ' ( yes "" | git mergetool subdir ) && - test "$(cat subdir/file3)" = "master new sub" && - git reset --hard + test "$(cat subdir/file3)" = "master new sub" ' test_expect_success 'mergetool delete/delete conflict' ' + test_when_finished "git reset --hard HEAD" && git checkout move-to-c && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && @@ -288,29 +292,30 @@ test_expect_success 'mergetool delete/delete conflict' ' git reset --hard HEAD && test_must_fail git merge move-to-b && ! echo a | git mergetool a/a/file.txt && - ! test -f a/a/file.txt && - git reset --hard HEAD + ! test -f a/a/file.txt ' test_expect_success 'mergetool produces no errors when keepBackup is used' ' + test_when_finished "git reset --hard HEAD" && test_config mergetool.keepBackup true && test_must_fail git merge move-to-b && : >expect && echo d | git mergetool a/a/file.txt 2>actual && test_cmp expect actual && - ! test -d a && - git reset --hard HEAD + ! test -d a ' test_expect_success 'mergetool honors tempfile config for deleted files' ' + test_when_finished "git reset --hard HEAD" && test_config mergetool.keepTemporaries false && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && - ! test -d a && - git reset --hard HEAD + ! test -d a ' test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' + test_when_finished "git reset --hard HEAD" && + test_when_finished "git clean -fdx" && test_config mergetool.keepTemporaries true && test_must_fail git merge move-to-b && ! (echo a; echo n) | git mergetool a/a/file.txt && @@ -321,12 +326,11 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' file_REMOTE_.txt EOF ls -1 a/a | sed -e "s/[0-9]*//g" >actual && - test_cmp expect actual && - git clean -fdx && - git reset --hard HEAD + test_cmp expect actual ' test_expect_success 'deleted vs modified submodule' ' + test_when_finished "git reset --hard HEAD" && git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && @@ -391,8 +395,7 @@ test_expect_success 'deleted vs modified submodule' ' test "$(cat submod/bar)" = "master submodule" && output="$(git mergetool --no-prompt)" && test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" && - git reset --hard HEAD + git commit -m "Merge resolved by keeping module" ' test_expect_success 'file vs modified submodule' ' @@ -479,6 +482,7 @@ test_expect_success 'submodule in subdirectory' ' git commit -m "add initial versions" ) ) && + test_when_finished "rm -rf subdir/subdir_module" && git submodule add git://example.com/subsubmodule subdir/subdir_module && git add subdir/subdir_module && git commit -m "add submodule in subdirectory" && @@ -523,8 +527,7 @@ test_expect_success 'submodule in subdirectory' ' test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && test "$(cat subdir/subdir_module/file15)" = "test$test_count.a" && - git commit -m "branch1 resolved with mergetool" && - rm -rf subdir/subdir_module + git commit -m "branch1 resolved with mergetool" ' test_expect_success 'directory vs modified submodule' ' @@ -578,34 +581,34 @@ test_expect_success 'directory vs modified submodule' ' ' test_expect_success 'file with no base' ' + test_when_finished "git reset --hard master >/dev/null 2>&1" && git checkout -b test$test_count branch1 && test_must_fail git merge master && git mergetool --no-prompt --tool mybase -- both && >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + test_cmp both expected ' test_expect_success 'custom commands override built-ins' ' + test_when_finished "git reset --hard master >/dev/null 2>&1" && git checkout -b test$test_count branch1 && test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && test_config mergetool.defaults.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool defaults -- both && echo master both added >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + test_cmp both expected ' test_expect_success 'filenames seen by tools start with ./' ' + test_when_finished "git reset --hard master >/dev/null 2>&1" && git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp false && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && - grep ^\./both_LOCAL_ actual >/dev/null && - git reset --hard master >/dev/null 2>&1 + grep ^\./both_LOCAL_ actual >/dev/null ' test_lazy_prereq MKTEMP ' @@ -615,6 +618,7 @@ test_lazy_prereq MKTEMP ' ' test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' + test_when_finished "git reset --hard master >/dev/null 2>&1" && git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp true && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && @@ -622,11 +626,11 @@ test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToT test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && test_must_fail grep ^\./both_LOCAL_ actual >/dev/null && - grep /both_LOCAL_ actual >/dev/null && - git reset --hard master >/dev/null 2>&1 + grep /both_LOCAL_ actual >/dev/null ' test_expect_success 'diff.orderFile configuration is honored' ' + test_when_finished "git reset --hard >/dev/null" && git checkout order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && @@ -641,10 +645,10 @@ test_expect_success 'diff.orderFile configuration is honored' ' EOF git mergetool --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && - test_cmp expect actual && - git reset --hard >/dev/null + test_cmp expect actual ' test_expect_success 'mergetool -Oorder-file is honored' ' + test_when_finished "git reset --hard >/dev/null 2>&1" && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && @@ -668,8 +672,7 @@ test_expect_success 'mergetool -Oorder-file is honored' ' EOF git mergetool -Oorder-file --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && - test_cmp expect actual && - git reset --hard >/dev/null 2>&1 + test_cmp expect actual ' test_done From b696ac9aafd57fad68383b4edd31784462d34bcb Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:54 -0500 Subject: [PATCH 0211/1540] t7610: don't rely on state from previous test If the repository must be in a particular state (beyond what is already done by the 'setup' test case) before the test can run, make the necessary repository changes in the test script even if it means duplicating some lines of code from the previous test case. This is a step toward making the tests more independent so that if one test fails it doesn't cause subsequent tests to fail. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index ca99c4b1adfabb..79545ec6620835 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -181,8 +181,12 @@ test_expect_success 'mergetool in subdir' ' ' test_expect_success 'mergetool on file in parent dir' ' + git reset --hard && + git submodule update -N && ( cd subdir && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) && ( yes "" | git mergetool ../both >/dev/null 2>&1 ) && @@ -652,6 +656,8 @@ test_expect_success 'mergetool -Oorder-file is honored' ' test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && + echo b >order-file && + echo a >>order-file && test_must_fail git merge order-file-side1 && cat >expect <<-\EOF && Merging: From c3ad3126b8113e9ed90ec8639a2ef266e9190565 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:55 -0500 Subject: [PATCH 0212/1540] t7610: run 'git reset --hard' after each test to clean up Use test_when_finished to run 'git reset --hard' after each test so that the repository is left in a saner state for the next test. This is a step toward making the tests more independent so that if one test fails it doesn't cause subsequent tests to fail. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 79545ec6620835..df6b4c579c3a7b 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -127,6 +127,7 @@ test_expect_success 'setup' ' ' test_expect_success 'custom mergetool' ' + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && @@ -170,6 +171,7 @@ test_expect_success 'mergetool crlf' ' ' test_expect_success 'mergetool in subdir' ' + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && git submodule update -N && ( @@ -181,6 +183,7 @@ test_expect_success 'mergetool in subdir' ' ' test_expect_success 'mergetool on file in parent dir' ' + test_when_finished "git reset --hard" && git reset --hard && git submodule update -N && ( @@ -214,6 +217,7 @@ test_expect_success 'mergetool skips autoresolved' ' ' test_expect_success 'mergetool merges all from subdir' ' + test_when_finished "git reset --hard" && test_config rerere.enabled false && ( cd subdir && @@ -244,6 +248,7 @@ test_expect_success 'mergetool skips resolved paths when rerere is active' ' ' test_expect_success 'conflicted stash sets up rerere' ' + test_when_finished "git reset --hard" && test_config rerere.enabled true && git checkout stash1 && echo "Conflicting stash content" >file11 && @@ -403,6 +408,7 @@ test_expect_success 'deleted vs modified submodule' ' ' test_expect_success 'file vs modified submodule' ' + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && @@ -474,6 +480,7 @@ test_expect_success 'file vs modified submodule' ' ' test_expect_success 'submodule in subdirectory' ' + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && git submodule update -N && ( @@ -535,6 +542,7 @@ test_expect_success 'submodule in subdirectory' ' ' test_expect_success 'directory vs modified submodule' ' + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && mv submod submod-movedaside && git rm --cached submod && From e866ff851a9cfed8a3ef24dc98e5cd6177ae0618 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:56 -0500 Subject: [PATCH 0213/1540] t7610: delete some now-unnecessary 'git reset --hard' lines Tests now always run 'git reset --hard' at the end (even if they fail), so it's no longer necessary to run 'git reset --hard' at the beginning of a test. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index df6b4c579c3a7b..28b5f847a55142 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -184,7 +184,6 @@ test_expect_success 'mergetool in subdir' ' test_expect_success 'mergetool on file in parent dir' ' test_when_finished "git reset --hard" && - git reset --hard && git submodule update -N && ( cd subdir && @@ -277,7 +276,6 @@ test_expect_success 'conflicted stash sets up rerere' ' test_expect_success 'mergetool takes partial path' ' test_when_finished "git reset --hard" && - git reset --hard && test_config rerere.enabled false && git checkout -b test$test_count branch1 && git submodule update -N && From 61b76d2de3e508e628cbb9a5718771309c2d4a81 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:57 -0500 Subject: [PATCH 0214/1540] t7610: always work on a test-specific branch Create and use a test-specific branch when the test might create a commit. This is not always necessary for correctness, but it improves debuggability by ensuring a commit created by test #N shows up on the testN branch, not the branch for test #N-1. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 28b5f847a55142..87562d8d4d76a0 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -184,6 +184,7 @@ test_expect_success 'mergetool in subdir' ' test_expect_success 'mergetool on file in parent dir' ' test_when_finished "git reset --hard" && + git checkout -b test$test_count && git submodule update -N && ( cd subdir && @@ -217,6 +218,7 @@ test_expect_success 'mergetool skips autoresolved' ' test_expect_success 'mergetool merges all from subdir' ' test_when_finished "git reset --hard" && + git checkout -b test$test_count && test_config rerere.enabled false && ( cd subdir && @@ -288,7 +290,7 @@ test_expect_success 'mergetool takes partial path' ' test_expect_success 'mergetool delete/delete conflict' ' test_when_finished "git reset --hard HEAD" && - git checkout move-to-c && + git checkout -b test$test_count move-to-c && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && ! test -f a/a/file.txt && @@ -304,6 +306,7 @@ test_expect_success 'mergetool delete/delete conflict' ' test_expect_success 'mergetool produces no errors when keepBackup is used' ' test_when_finished "git reset --hard HEAD" && + git checkout -b test$test_count && test_config mergetool.keepBackup true && test_must_fail git merge move-to-b && : >expect && @@ -314,6 +317,7 @@ test_expect_success 'mergetool produces no errors when keepBackup is used' ' test_expect_success 'mergetool honors tempfile config for deleted files' ' test_when_finished "git reset --hard HEAD" && + git checkout -b test$test_count && test_config mergetool.keepTemporaries false && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && @@ -323,6 +327,7 @@ test_expect_success 'mergetool honors tempfile config for deleted files' ' test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' test_when_finished "git reset --hard HEAD" && test_when_finished "git clean -fdx" && + git checkout -b test$test_count && test_config mergetool.keepTemporaries true && test_must_fail git merge move-to-b && ! (echo a; echo n) | git mergetool a/a/file.txt && @@ -641,7 +646,7 @@ test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToT test_expect_success 'diff.orderFile configuration is honored' ' test_when_finished "git reset --hard >/dev/null" && - git checkout order-file-side2 && + git checkout -b test$test_count order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && @@ -659,6 +664,7 @@ test_expect_success 'diff.orderFile configuration is honored' ' ' test_expect_success 'mergetool -Oorder-file is honored' ' test_when_finished "git reset --hard >/dev/null 2>&1" && + git checkout -b test$test_count && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && From fef6c06d6401458f47b5beb9822c75c74a2eb07f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:58 -0500 Subject: [PATCH 0215/1540] t7610: don't assume the checked-out commit Always check out the required commit at the beginning of the test so that a failure in a previous test does not cause the test to work off of the wrong commit. This is a step toward making the tests more independent so that if one test fails it doesn't cause subsequent tests to fail. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 87562d8d4d76a0..e8ada8f1289da6 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -184,7 +184,7 @@ test_expect_success 'mergetool in subdir' ' test_expect_success 'mergetool on file in parent dir' ' test_when_finished "git reset --hard" && - git checkout -b test$test_count && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -218,7 +218,7 @@ test_expect_success 'mergetool skips autoresolved' ' test_expect_success 'mergetool merges all from subdir' ' test_when_finished "git reset --hard" && - git checkout -b test$test_count && + git checkout -b test$test_count branch1 && test_config rerere.enabled false && ( cd subdir && @@ -306,7 +306,7 @@ test_expect_success 'mergetool delete/delete conflict' ' test_expect_success 'mergetool produces no errors when keepBackup is used' ' test_when_finished "git reset --hard HEAD" && - git checkout -b test$test_count && + git checkout -b test$test_count move-to-c && test_config mergetool.keepBackup true && test_must_fail git merge move-to-b && : >expect && @@ -317,7 +317,7 @@ test_expect_success 'mergetool produces no errors when keepBackup is used' ' test_expect_success 'mergetool honors tempfile config for deleted files' ' test_when_finished "git reset --hard HEAD" && - git checkout -b test$test_count && + git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries false && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && @@ -327,7 +327,7 @@ test_expect_success 'mergetool honors tempfile config for deleted files' ' test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' test_when_finished "git reset --hard HEAD" && test_when_finished "git clean -fdx" && - git checkout -b test$test_count && + git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries true && test_must_fail git merge move-to-b && ! (echo a; echo n) | git mergetool a/a/file.txt && @@ -664,7 +664,7 @@ test_expect_success 'diff.orderFile configuration is honored' ' ' test_expect_success 'mergetool -Oorder-file is honored' ' test_when_finished "git reset --hard >/dev/null 2>&1" && - git checkout -b test$test_count && + git checkout -b test$test_count order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && From bd9714f2533e8264422811098d0e1b13134094e9 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:41:59 -0500 Subject: [PATCH 0216/1540] t7610: spell 'git reset --hard' consistently Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index e8ada8f1289da6..de006edf07c529 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -289,23 +289,23 @@ test_expect_success 'mergetool takes partial path' ' ' test_expect_success 'mergetool delete/delete conflict' ' - test_when_finished "git reset --hard HEAD" && + test_when_finished "git reset --hard" && git checkout -b test$test_count move-to-c && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && ! test -f a/a/file.txt && - git reset --hard HEAD && + git reset --hard && test_must_fail git merge move-to-b && echo m | git mergetool a/a/file.txt && test -f b/b/file.txt && - git reset --hard HEAD && + git reset --hard && test_must_fail git merge move-to-b && ! echo a | git mergetool a/a/file.txt && ! test -f a/a/file.txt ' test_expect_success 'mergetool produces no errors when keepBackup is used' ' - test_when_finished "git reset --hard HEAD" && + test_when_finished "git reset --hard" && git checkout -b test$test_count move-to-c && test_config mergetool.keepBackup true && test_must_fail git merge move-to-b && @@ -316,7 +316,7 @@ test_expect_success 'mergetool produces no errors when keepBackup is used' ' ' test_expect_success 'mergetool honors tempfile config for deleted files' ' - test_when_finished "git reset --hard HEAD" && + test_when_finished "git reset --hard" && git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries false && test_must_fail git merge move-to-b && @@ -325,7 +325,7 @@ test_expect_success 'mergetool honors tempfile config for deleted files' ' ' test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' - test_when_finished "git reset --hard HEAD" && + test_when_finished "git reset --hard" && test_when_finished "git clean -fdx" && git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries true && @@ -342,7 +342,7 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' ' test_expect_success 'deleted vs modified submodule' ' - test_when_finished "git reset --hard HEAD" && + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && @@ -560,7 +560,7 @@ test_expect_success 'directory vs modified submodule' ' test "$(cat submod/file16)" = "not a submodule" && rm -rf submod.orig && - git reset --hard >/dev/null 2>&1 && + git reset --hard && test_must_fail git merge master && test -n "$(git ls-files -u)" && test ! -e submod.orig && @@ -572,7 +572,8 @@ test_expect_success 'directory vs modified submodule' ' ( cd submod && git clean -f && git reset --hard ) && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && + git reset --hard && + rm -rf submod-movedaside && git checkout -b test$test_count.c master && git submodule update -N && @@ -582,7 +583,7 @@ test_expect_success 'directory vs modified submodule' ' git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && + git reset --hard && git submodule update -N && test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && @@ -590,13 +591,13 @@ test_expect_success 'directory vs modified submodule' ' ( yes "r" | git mergetool submod ) && test "$(cat submod/file16)" = "not a submodule" && - git reset --hard master >/dev/null 2>&1 && + git reset --hard master && ( cd submod && git clean -f && git reset --hard ) && git submodule update -N ' test_expect_success 'file with no base' ' - test_when_finished "git reset --hard master >/dev/null 2>&1" && + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && test_must_fail git merge master && git mergetool --no-prompt --tool mybase -- both && @@ -605,7 +606,7 @@ test_expect_success 'file with no base' ' ' test_expect_success 'custom commands override built-ins' ' - test_when_finished "git reset --hard master >/dev/null 2>&1" && + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && test_config mergetool.defaults.trustExitCode true && @@ -616,7 +617,7 @@ test_expect_success 'custom commands override built-ins' ' ' test_expect_success 'filenames seen by tools start with ./' ' - test_when_finished "git reset --hard master >/dev/null 2>&1" && + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp false && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && @@ -633,7 +634,7 @@ test_lazy_prereq MKTEMP ' ' test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' - test_when_finished "git reset --hard master >/dev/null 2>&1" && + test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp true && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && @@ -645,7 +646,7 @@ test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToT ' test_expect_success 'diff.orderFile configuration is honored' ' - test_when_finished "git reset --hard >/dev/null" && + test_when_finished "git reset --hard" && git checkout -b test$test_count order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && @@ -663,7 +664,7 @@ test_expect_success 'diff.orderFile configuration is honored' ' test_cmp expect actual ' test_expect_success 'mergetool -Oorder-file is honored' ' - test_when_finished "git reset --hard >/dev/null 2>&1" && + test_when_finished "git reset --hard" && git checkout -b test$test_count order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && @@ -679,7 +680,7 @@ test_expect_success 'mergetool -Oorder-file is honored' ' git mergetool -O/dev/null --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && test_cmp expect actual && - git reset --hard >/dev/null 2>&1 && + git reset --hard && git config --unset diff.orderFile && test_must_fail git merge order-file-side1 && From b9ebb659268ae1ecf742dff6261e87f4ee57dde7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:42:00 -0500 Subject: [PATCH 0217/1540] t7610: add test case for rerere+mergetool+subdir bug If rerere is enabled and mergetool is run from a subdirectory, mergetool always prints "No files need merging". Add an expected failure test case for this situation. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- t/t7610-mergetool.sh | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index de006edf07c529..ab0b005b28e268 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -216,7 +216,7 @@ test_expect_success 'mergetool skips autoresolved' ' test "$output" = "No files need merging" ' -test_expect_success 'mergetool merges all from subdir' ' +test_expect_success 'mergetool merges all from subdir (rerere disabled)' ' test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && test_config rerere.enabled false && @@ -234,6 +234,25 @@ test_expect_success 'mergetool merges all from subdir' ' ) ' +test_expect_failure 'mergetool merges all from subdir (rerere enabled)' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && + test_config rerere.enabled true && + rm -rf .git/rr-cache && + ( + cd subdir && + test_must_fail git merge master && + ( yes "r" | git mergetool ../submod ) && + ( yes "d" "d" | git mergetool --no-prompt ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat file3)" = "master new sub" && + ( cd .. && git submodule update -N ) && + test "$(cat ../submod/bar)" = "master submodule" && + git commit -m "branch2 resolved by mergetool from subdir" + ) +' + test_expect_success 'mergetool skips resolved paths when rerere is active' ' test_when_finished "git reset --hard" && test_config rerere.enabled true && From c1b0d3a010eaed0d7683fe0593132f2211467b20 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:42:01 -0500 Subject: [PATCH 0218/1540] mergetool: take the "-O" out of $orderfile This will make it easier for a future commit to convert a relative orderfile pathname to either absolute or relative to the top-level directory. It also improves code readability. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- git-mergetool.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-mergetool.sh b/git-mergetool.sh index e52b4e4f24088d..b506896dc1fcd8 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -421,7 +421,7 @@ main () { prompt=true ;; -O*) - orderfile="$1" + orderfile="${1#-O}" ;; --) shift @@ -465,7 +465,7 @@ main () { files=$(git -c core.quotePath=false \ diff --name-only --diff-filter=U \ - ${orderfile:+"$orderfile"} -- "$@") + ${orderfile:+"-O$orderfile"} -- "$@") cd_to_toplevel From d0e0cfe745e0c88ca2f4d4ed9f1d2871b659d872 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 10 Jan 2017 15:42:02 -0500 Subject: [PATCH 0219/1540] mergetool: fix running in subdir when rerere enabled "git mergetool" (without any pathspec on the command line) that is not run from the top-level of the working tree no longer works in Git v2.11, failing to get the list of unmerged paths from the output of "git rerere remaining". This regression was introduced by 57937f70a0 ("mergetool: honor diff.orderFile", 2016-10-07). This is because the pathnames output by the 'git rerere remaining' command are relative to the top-level directory but the 'git diff --name-only' command expects its pathname arguments to be relative to the current working directory. To make everything consistent, cd_to_toplevel before running 'git diff --name-only' and adjust any relative pathnames. Signed-off-by: Richard Hansen Signed-off-by: Junio C Hamano --- git-mergetool.sh | 17 +++++++++++++++-- t/t7610-mergetool.sh | 7 ++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/git-mergetool.sh b/git-mergetool.sh index b506896dc1fcd8..c062e3de3a503b 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -454,6 +454,17 @@ main () { merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)" merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)" + prefix=$(git rev-parse --show-prefix) || exit 1 + cd_to_toplevel + + if test -n "$orderfile" + then + orderfile=$( + git rev-parse --prefix "$prefix" -- "$orderfile" | + sed -e 1d + ) + fi + if test $# -eq 0 && test -e "$GIT_DIR/MERGE_RR" then set -- $(git rerere remaining) @@ -461,14 +472,16 @@ main () { then print_noop_and_exit fi + elif test $# -ge 0 + then + # rev-parse provides the -- needed for 'set' + eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")" fi files=$(git -c core.quotePath=false \ diff --name-only --diff-filter=U \ ${orderfile:+"-O$orderfile"} -- "$@") - cd_to_toplevel - if test -z "$files" then print_noop_and_exit diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index ab0b005b28e268..180dd7057a7ca1 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -234,7 +234,7 @@ test_expect_success 'mergetool merges all from subdir (rerere disabled)' ' ) ' -test_expect_failure 'mergetool merges all from subdir (rerere enabled)' ' +test_expect_success 'mergetool merges all from subdir (rerere enabled)' ' test_when_finished "git reset --hard" && git checkout -b test$test_count branch1 && test_config rerere.enabled true && @@ -678,6 +678,11 @@ test_expect_success 'diff.orderFile configuration is honored' ' b a EOF + + # make sure "order-file" that is ambiguous between + # rev and path is understood correctly. + git branch order-file HEAD && + git mergetool --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && test_cmp expect actual From d7dffce1cebde29a0c4b309a79e4345450bf352a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 10 Jan 2017 15:25:46 -0800 Subject: [PATCH 0220/1540] Fifth batch 2.12 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.12.0.txt | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index 778145a3ed322f..2a19064f6e4b1e 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -61,6 +61,11 @@ UI, Workflows & Features the previous hack that depended on internals of (older) MSVC runtime. + * Some platforms no longer understand "latin-1" that is still seen in + the wild in e-mail headers; replace them with "iso-8859-1" that is + more widely known when conversion fails from/to it. + (merge df3755888b jc/latin-1 later to maint). + Performance, Internal Implementation, Development Support etc. @@ -81,6 +86,20 @@ Performance, Internal Implementation, Development Support etc. * Update the procedure to generate "tags" for developer support. (merge 046e4c1c09 jk/make-tags-find-sources-tweak later to maint). + * The codeflow of setting NOATIME and CLOEXEC on file descriptors Git + opens has been simplified. + (merge b4d065df03 jc/git-open-cloexec later to maint). + + * "git diff" and its family had two experimental heuristics to shift + the contents of a hunk to make the patch easier to read. One of + them turns out to be better than the other, so leave only the + "--indent-heuristic" option and remove the other one. + (merge 3cde4e02ee jc/retire-compaction-heuristics later to maint). + + * A new submodule helper "git submodule embedgitdirs" to make it + easier to move embedded .git/ directory for submodules in a + superproject to .git/modules/ (and point the latter with the former + that is turned into a "gitdir:" file) has been added. Also contains various documentation updates and code clean-ups. @@ -242,6 +261,29 @@ notes for details). fixed. (merge c46458e82f mk/mingw-winansi-ttyname-termination-fix later to maint). + * When the http server gives an incomplete response to a smart-http + rpc call, it could lead to client waiting for a full response that + will never come. Teach the client side to notice this condition + and abort the transfer. + (merge f8edeaa05d dt/smart-http-detect-server-going-away later to maint). + + * Compression setting for producing packfiles were spread across + three codepaths, one of which did not honor any configuration. + Unify these so that all of them honor core.compression and + pack.compression variables the same way. + (merge 8de7eeb54b jc/compression-config later to maint). + + * "git fast-import" sometimes mishandled while rebalancing notes + tree, which has been fixed. + (merge 405d7f4af6 mh/fast-import-notes-fix-new later to maint). + + * Recent update to the default abbreviation length that auto-scales + lacked documentation update, which has been corrected. + (merge 48d5014dd4 jc/abbrev-autoscale-config later to maint). + + * Leakage of lockfiles in the config subsystem has been fixed. + (merge c06fa62dfc nd/config-misc-fixes later to maint). + * Other minor doc, test and build updates and code cleanups. (merge fa6ca11105 nd/qsort-in-merge-recursive later to maint). (merge fa3142c919 ak/lazy-prereq-mktemp later to maint). @@ -253,3 +295,6 @@ notes for details). (merge 47437fd3bd kh/tutorial-grammofix later to maint). (merge f2627d9b19 sb/submodule-config-cleanup later to maint). (merge 7eeda8b821 ls/filter-process later to maint). + (merge 6cc823c5c1 jt/fetch-no-redundant-tag-fetch-map later to maint). + (merge 235ec24352 mm/push-social-engineering-attack-doc later to maint). + (merge f1350d0c12 mm/gc-safety-doc later to maint). From 7675c7bd01b8d39dc3c1e8385403f0307be31712 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 11 Jan 2017 06:10:55 -0500 Subject: [PATCH 0221/1540] t7810: avoid assumption about invalid regex syntax A few of the tests want to check that "git grep -P -E" will override -P with -E, and vice versa. To do so, we use a regex with "\x{..}", which is valid in PCRE but not defined by POSIX (for basic or extended regular expressions). However, POSIX declares quite a lot of syntax, including "\x", as "undefined". That leaves implementations free to extend the standard if they choose. At least one, musl libc, implements "\x" in the same way as PCRE. Our tests check that "-E" complains about "\x", which fails with musl. We can fix this by finding some construct which behaves reliably on both PCRE and POSIX, but differently in each system. One such construct is the use of backslash inside brackets. In PCRE, "[\d]" interprets "\d" as it would outside the brackets, matching a digit. Whereas in POSIX, the backslash must be treated literally, and we match either it or a literal "d". Moreover, implementations are not free to change this according to POSIX, so we should be able to rely on it. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t7810-grep.sh | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 1e72971a165efc..d69f76bf906e7b 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -37,6 +37,10 @@ test_expect_success setup ' echo "a+bc" echo "abc" } >ab && + { + echo d && + echo 0 + } >d0 && echo vvv >v && echo ww w >w && echo x x xx x >x && @@ -1092,36 +1096,36 @@ test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =extended ' test_expect_success 'grep -G -F -P -E pattern' ' - >empty && - test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual && - test_cmp empty actual + echo "d0:d" >expected && + git grep -G -F -P -E "[\d]" d0 >actual && + test_cmp expected actual ' test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =perl, =extended' ' - >empty && - test_must_fail git \ + echo "d0:d" >expected && + git \ -c grep.patterntype=fixed \ -c grep.patterntype=basic \ -c grep.patterntype=perl \ -c grep.patterntype=extended \ - grep "a\x{2b}b\x{2a}c" ab >actual && - test_cmp empty actual + grep "[\d]" d0 >actual && + test_cmp expected actual ' test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' - echo "ab:a+b*c" >expected && - git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual && + echo "d0:0" >expected && + git grep -G -F -E -P "[\d]" d0 >actual && test_cmp expected actual ' test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' ' - echo "ab:a+b*c" >expected && + echo "d0:0" >expected && git \ -c grep.patterntype=fixed \ -c grep.patterntype=basic \ -c grep.patterntype=extended \ -c grep.patterntype=perl \ - grep "a\x{2b}b\x{2a}c" ab >actual && + grep "[\d]" d0 >actual && test_cmp expected actual ' From 84a7f09625685b2093ff0221ad855f5f783b4b4a Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 17:45:39 -0800 Subject: [PATCH 0222/1540] read-tree: use OPT_BOOL instead of OPT_SET_INT All occurrences of OPT_SET_INT were setting the value to 1; internally OPT_BOOL is just that. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- builtin/read-tree.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index fa6edb35b21ced..8ba64bc7856705 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -109,34 +109,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"), N_("write resulting index to "), PARSE_OPT_NONEG, index_output_cb }, - OPT_SET_INT(0, "empty", &read_empty, - N_("only empty the index"), 1), + OPT_BOOL(0, "empty", &read_empty, + N_("only empty the index")), OPT__VERBOSE(&opts.verbose_update, N_("be verbose")), OPT_GROUP(N_("Merging")), - OPT_SET_INT('m', NULL, &opts.merge, - N_("perform a merge in addition to a read"), 1), - OPT_SET_INT(0, "trivial", &opts.trivial_merges_only, - N_("3-way merge if no file level merging required"), 1), - OPT_SET_INT(0, "aggressive", &opts.aggressive, - N_("3-way merge in presence of adds and removes"), 1), - OPT_SET_INT(0, "reset", &opts.reset, - N_("same as -m, but discard unmerged entries"), 1), + OPT_BOOL('m', NULL, &opts.merge, + N_("perform a merge in addition to a read")), + OPT_BOOL(0, "trivial", &opts.trivial_merges_only, + N_("3-way merge if no file level merging required")), + OPT_BOOL(0, "aggressive", &opts.aggressive, + N_("3-way merge in presence of adds and removes")), + OPT_BOOL(0, "reset", &opts.reset, + N_("same as -m, but discard unmerged entries")), { OPTION_STRING, 0, "prefix", &opts.prefix, N_("/"), N_("read the tree into the index under /"), PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP }, - OPT_SET_INT('u', NULL, &opts.update, - N_("update working tree with merge result"), 1), + OPT_BOOL('u', NULL, &opts.update, + N_("update working tree with merge result")), { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, N_("gitignore"), N_("allow explicitly ignored files to be overwritten"), PARSE_OPT_NONEG, exclude_per_directory_cb }, - OPT_SET_INT('i', NULL, &opts.index_only, - N_("don't check the working tree after merging"), 1), + OPT_BOOL('i', NULL, &opts.index_only, + N_("don't check the working tree after merging")), OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")), - OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, - N_("skip applying sparse checkout filter"), 1), - OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, - N_("debug unpack-trees"), 1), + OPT_BOOL(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + N_("skip applying sparse checkout filter")), + OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, + N_("debug unpack-trees")), OPT_END() }; From 0b8b25f610b60f7a1c5f2608c3a3aaf409013b1b Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 17:45:40 -0800 Subject: [PATCH 0223/1540] t1000: modernize style The preferred style in tests is: test_expect_success 'short description then sq to open the body' ' here comes the test && and chains over many lines && with closing sq on its own line ' Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- t/t1000-read-tree-m-3way.sh | 648 ++++++++++++++++++------------------ 1 file changed, 315 insertions(+), 333 deletions(-) diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index a0b79b4839fb21..3c4d2d6045bf02 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -128,29 +128,29 @@ cat >expected <<\EOF EOF check_result () { - git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && - test_cmp expected current + git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && + test_cmp expected current } # This is done on an empty work directory, which is the normal # merge person behaviour. -test_expect_success \ - '3-way merge with git read-tree -m, empty cache' \ - "rm -fr [NDMALTS][NDMALTSF] Z && - rm .git/index && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" +test_expect_success '3-way merge with git read-tree -m, empty cache' ' + rm -fr [NDMALTS][NDMALTSF] Z && + rm .git/index && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' # This starts out with the first head, which is the normal # patch submitter behaviour. -test_expect_success \ - '3-way merge with git read-tree -m, match H' \ - "rm -fr [NDMALTS][NDMALTSF] Z && - rm .git/index && - read_tree_must_succeed $tree_A && - git checkout-index -f -u -a && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" +test_expect_success '3-way merge with git read-tree -m, match H' ' + rm -fr [NDMALTS][NDMALTSF] Z && + rm .git/index && + read_tree_must_succeed $tree_A && + git checkout-index -f -u -a && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' : <<\END_OF_CASE_TABLE @@ -208,322 +208,304 @@ DF (file) when tree B require DF to be a directory by having DF/DF END_OF_CASE_TABLE -test_expect_success '1 - must not have an entry not in A.' " - rm -f .git/index XX && - echo XX >XX && - git update-index --add XX && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '2 - must match B in !O && !A && B case.' \ - "rm -f .git/index NA && - cp .orig-B/NA NA && - git update-index --add NA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '2 - matching B alone is OK in !O && !A && B case.' \ - "rm -f .git/index NA && - cp .orig-B/NA NA && - git update-index --add NA && - echo extra >>NA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '3 - must match A in !O && A && !B case.' \ - "rm -f .git/index AN && - cp .orig-A/AN AN && - git update-index --add AN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '3 - matching A alone is OK in !O && A && !B case.' \ - "rm -f .git/index AN && - cp .orig-A/AN AN && - git update-index --add AN && - echo extra >>AN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '3 (fail) - must match A in !O && A && !B case.' " - rm -f .git/index AN && - cp .orig-A/AN AN && - echo extra >>AN && - git update-index --add AN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '4 - must match and be up-to-date in !O && A && B && A!=B case.' \ - "rm -f .git/index AA && - cp .orig-A/AA AA && - git update-index --add AA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " - rm -f .git/index AA && - cp .orig-A/AA AA && - git update-index --add AA && - echo extra >>AA && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " - rm -f .git/index AA && - cp .orig-A/AA AA && - echo extra >>AA && - git update-index --add AA && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '5 - must match in !O && A && B && A==B case.' \ - "rm -f .git/index LL && - cp .orig-A/LL LL && - git update-index --add LL && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '5 - must match in !O && A && B && A==B case.' \ - "rm -f .git/index LL && - cp .orig-A/LL LL && - git update-index --add LL && - echo extra >>LL && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '5 (fail) - must match A in !O && A && B && A==B case.' " - rm -f .git/index LL && - cp .orig-A/LL LL && - echo extra >>LL && - git update-index --add LL && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '6 - must not exist in O && !A && !B case' " - rm -f .git/index DD && - echo DD >DD && - git update-index --add DD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '7 - must not exist in O && !A && B && O!=B case' " - rm -f .git/index DM && - cp .orig-B/DM DM && - git update-index --add DM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '8 - must not exist in O && !A && B && O==B case' " - rm -f .git/index DN && - cp .orig-B/DN DN && - git update-index --add DN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '9 - must match and be up-to-date in O && A && !B && O!=A case' \ - "rm -f .git/index MD && - cp .orig-A/MD MD && - git update-index --add MD && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " - rm -f .git/index MD && - cp .orig-A/MD MD && - git update-index --add MD && - echo extra >>MD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " - rm -f .git/index MD && - cp .orig-A/MD MD && - echo extra >>MD && - git update-index --add MD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '10 - must match and be up-to-date in O && A && !B && O==A case' \ - "rm -f .git/index ND && - cp .orig-A/ND ND && - git update-index --add ND && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " - rm -f .git/index ND && - cp .orig-A/ND ND && - git update-index --add ND && - echo extra >>ND && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " - rm -f .git/index ND && - cp .orig-A/ND ND && - echo extra >>ND && - git update-index --add ND && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \ - "rm -f .git/index MM && - cp .orig-A/MM MM && - git update-index --add MM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " - rm -f .git/index MM && - cp .orig-A/MM MM && - git update-index --add MM && - echo extra >>MM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " - rm -f .git/index MM && - cp .orig-A/MM MM && - echo extra >>MM && - git update-index --add MM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '12 - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && - cp .orig-A/SS SS && - git update-index --add SS && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '12 - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && - cp .orig-A/SS SS && - git update-index --add SS && - echo extra >>SS && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '12 (fail) - must match A in O && A && B && O!=A && A==B case' " - rm -f .git/index SS && - cp .orig-A/SS SS && - echo extra >>SS && - git update-index --add SS && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '13 - must match A in O && A && B && O!=A && O==B case' \ - "rm -f .git/index MN && - cp .orig-A/MN MN && - git update-index --add MN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '13 - must match A in O && A && B && O!=A && O==B case' \ - "rm -f .git/index MN && - cp .orig-A/MN MN && - git update-index --add MN && - echo extra >>MN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && - cp .orig-A/NM NM && - git update-index --add NM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 - may match B in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && - cp .orig-B/NM NM && - git update-index --add NM && - echo extra >>NM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " - rm -f .git/index NM && - cp .orig-A/NM NM && - git update-index --add NM && - echo extra >>NM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " - rm -f .git/index NM && - cp .orig-A/NM NM && - echo extra >>NM && - git update-index --add NM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '15 - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && - cp .orig-A/NN NN && - git update-index --add NN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '15 - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && - cp .orig-A/NN NN && - git update-index --add NN && - echo extra >>NN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '15 (fail) - must match A in O && A && B && O==A && O==B case' " - rm -f .git/index NN && - cp .orig-A/NN NN && - echo extra >>NN && - git update-index --add NN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -# #16 -test_expect_success \ - '16 - A matches in one and B matches in another.' \ - 'rm -f .git/index F16 && - echo F16 >F16 && - git update-index --add F16 && - tree0=$(git write-tree) && - echo E16 >F16 && - git update-index F16 && - tree1=$(git write-tree) && - read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && - git ls-files --stage' +test_expect_success '1 - must not have an entry not in A.' ' + rm -f .git/index XX && + echo XX >XX && + git update-index --add XX && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '2 - must match B in !O && !A && B case.' ' + rm -f .git/index NA && + cp .orig-B/NA NA && + git update-index --add NA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '2 - matching B alone is OK in !O && !A && B case.' ' + rm -f .git/index NA && + cp .orig-B/NA NA && + git update-index --add NA && + echo extra >>NA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '3 - must match A in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + git update-index --add AN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '3 - matching A alone is OK in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + git update-index --add AN && + echo extra >>AN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '3 (fail) - must match A in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + echo extra >>AN && + git update-index --add AN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '4 - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + git update-index --add AA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + git update-index --add AA && + echo extra >>AA && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + echo extra >>AA && + git update-index --add AA && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '5 - must match in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + git update-index --add LL && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '5 - must match in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + git update-index --add LL && + echo extra >>LL && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '5 (fail) - must match A in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + echo extra >>LL && + git update-index --add LL && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '6 - must not exist in O && !A && !B case' ' + rm -f .git/index DD && + echo DD >DD && + git update-index --add DD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '7 - must not exist in O && !A && B && O!=B case' ' + rm -f .git/index DM && + cp .orig-B/DM DM && + git update-index --add DM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '8 - must not exist in O && !A && B && O==B case' ' + rm -f .git/index DN && + cp .orig-B/DN DN && + git update-index --add DN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '9 - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + git update-index --add MD && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + git update-index --add MD && + echo extra >>MD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + echo extra >>MD && + git update-index --add MD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '10 - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + git update-index --add ND && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + git update-index --add ND && + echo extra >>ND && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + echo extra >>ND && + git update-index --add ND && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + git update-index --add MM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + git update-index --add MM && + echo extra >>MM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + echo extra >>MM && + git update-index --add MM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + git update-index --add SS && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + git update-index --add SS && + echo extra >>SS && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '12 (fail) - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + echo extra >>SS && + git update-index --add SS && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' ' + rm -f .git/index MN && + cp .orig-A/MN MN && + git update-index --add MN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' ' + rm -f .git/index MN && + cp .orig-A/MN MN && + git update-index --add MN && + echo extra >>MN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + git update-index --add NM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 - may match B in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-B/NM NM && + git update-index --add NM && + echo extra >>NM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + git update-index --add NM && + echo extra >>NM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + echo extra >>NM && + git update-index --add NM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '15 - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + git update-index --add NN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '15 - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + git update-index --add NN && + echo extra >>NN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '15 (fail) - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + echo extra >>NN && + git update-index --add NN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '16 - A matches in one and B matches in another.' ' + rm -f .git/index F16 && + echo F16 >F16 && + git update-index --add F16 && + tree0=$(git write-tree) && + echo E16 >F16 && + git update-index F16 && + tree1=$(git write-tree) && + read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && + git ls-files --stage +' test_done From 83587d5236cbb4f45d6c0c551d0f0669ba25b176 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Mon, 9 Jan 2017 17:45:41 -0800 Subject: [PATCH 0224/1540] t1001: modernize style The preferred style in tests is: test_expect_success 'short description then sq to open the body' ' here comes the test && and chains over many lines && with closing sq on its own line ' Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- t/t1001-read-tree-m-2way.sh | 641 ++++++++++++++++++------------------ 1 file changed, 320 insertions(+), 321 deletions(-) diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index db1b6f5cf4dd8c..7b700897056593 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -14,10 +14,10 @@ all the combinations described in the two-tree merge "carry forward" rules, found in . In the test, these paths are used: - bozbar - in H, stays in M, modified from bozbar to gnusto - frotz - not in H added in M - nitfol - in H, stays in M unmodified - rezrov - in H, deleted in M + bozbar - in H, stays in M, modified from bozbar to gnusto + frotz - not in H added in M + nitfol - in H, stays in M unmodified + rezrov - in H, deleted in M yomin - not in H or M ' . ./test-lib.sh @@ -60,336 +60,335 @@ EOF sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new -test_expect_success \ - setup \ - 'echo frotz >frotz && - echo nitfol >nitfol && - cat bozbar-old >bozbar && - echo rezrov >rezrov && - echo yomin >yomin && - git update-index --add nitfol bozbar rezrov && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH && - - cat bozbar-new >bozbar && - git update-index --add frotz bozbar --force-remove rezrov && - git ls-files --stage >M.out && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git diff-tree $treeH $treeM' - -test_expect_success \ - '1, 2, 3 - no carry forward' \ - 'rm -f .git/index && - read_tree_twoway $treeH $treeM && - git ls-files --stage >1-3.out && - test_cmp M.out 1-3.out && - check_cache_at bozbar dirty && - check_cache_at frotz dirty && - check_cache_at nitfol dirty' +test_expect_success 'setup' ' + echo frotz >frotz && + echo nitfol >nitfol && + cat bozbar-old >bozbar && + echo rezrov >rezrov && + echo yomin >yomin && + git update-index --add nitfol bozbar rezrov && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH && + + cat bozbar-new >bozbar && + git update-index --add frotz bozbar --force-remove rezrov && + git ls-files --stage >M.out && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git diff-tree $treeH $treeM +' +test_expect_success '1, 2, 3 - no carry forward' ' + rm -f .git/index && + read_tree_twoway $treeH $treeM && + git ls-files --stage >1-3.out && + test_cmp M.out 1-3.out && + check_cache_at bozbar dirty && + check_cache_at frotz dirty && + check_cache_at nitfol dirty +' echo '+100644 X 0 yomin' >expected -test_expect_success \ - '4 - carry forward local addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - git update-index --add yomin && - read_tree_twoway $treeH $treeM && - git ls-files --stage >4.out && - test_must_fail git diff --no-index M.out 4.out >4diff.out && - compare_change 4diff.out expected && - check_cache_at yomin clean' - -test_expect_success \ - '5 - carry forward local addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo yomin >yomin && - git update-index --add yomin && - echo yomin yomin >yomin && - read_tree_twoway $treeH $treeM && - git ls-files --stage >5.out && - test_must_fail git diff --no-index M.out 5.out >5diff.out && - compare_change 5diff.out expected && - check_cache_at yomin dirty' - -test_expect_success \ - '6 - local addition already has the same.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - git update-index --add frotz && - read_tree_twoway $treeH $treeM && - git ls-files --stage >6.out && - test_cmp M.out 6.out && - check_cache_at frotz clean' - -test_expect_success \ - '7 - local addition already has the same.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz >frotz && - git update-index --add frotz && - echo frotz frotz >frotz && - read_tree_twoway $treeH $treeM && - git ls-files --stage >7.out && - test_cmp M.out 7.out && - check_cache_at frotz dirty' - -test_expect_success \ - '8 - conflicting addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz frotz >frotz && - git update-index --add frotz && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '9 - conflicting addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz frotz >frotz && - git update-index --add frotz && - echo frotz >frotz && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '10 - path removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov >rezrov && - git update-index --add rezrov && - read_tree_twoway $treeH $treeM && - git ls-files --stage >10.out && - test_cmp M.out 10.out' - -test_expect_success \ - '11 - dirty path removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov >rezrov && - git update-index --add rezrov && - echo rezrov rezrov >rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '12 - unmatching local changes being removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov rezrov >rezrov && - git update-index --add rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '13 - unmatching local changes being removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov rezrov >rezrov && - git update-index --add rezrov && - echo rezrov >rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '4 - carry forward local addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + git update-index --add yomin && + read_tree_twoway $treeH $treeM && + git ls-files --stage >4.out && + test_must_fail git diff --no-index M.out 4.out >4diff.out && + compare_change 4diff.out expected && + check_cache_at yomin clean +' + +test_expect_success '5 - carry forward local addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo yomin >yomin && + git update-index --add yomin && + echo yomin yomin >yomin && + read_tree_twoway $treeH $treeM && + git ls-files --stage >5.out && + test_must_fail git diff --no-index M.out 5.out >5diff.out && + compare_change 5diff.out expected && + check_cache_at yomin dirty +' + +test_expect_success '6 - local addition already has the same.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + git update-index --add frotz && + read_tree_twoway $treeH $treeM && + git ls-files --stage >6.out && + test_cmp M.out 6.out && + check_cache_at frotz clean +' + +test_expect_success '7 - local addition already has the same.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz >frotz && + git update-index --add frotz && + echo frotz frotz >frotz && + read_tree_twoway $treeH $treeM && + git ls-files --stage >7.out && + test_cmp M.out 7.out && + check_cache_at frotz dirty +' + +test_expect_success '8 - conflicting addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz frotz >frotz && + git update-index --add frotz && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '9 - conflicting addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz frotz >frotz && + git update-index --add frotz && + echo frotz >frotz && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '10 - path removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov >rezrov && + git update-index --add rezrov && + read_tree_twoway $treeH $treeM && + git ls-files --stage >10.out && + test_cmp M.out 10.out +' + +test_expect_success '11 - dirty path removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov >rezrov && + git update-index --add rezrov && + echo rezrov rezrov >rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '12 - unmatching local changes being removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov rezrov >rezrov && + git update-index --add rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '13 - unmatching local changes being removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov rezrov >rezrov && + git update-index --add rezrov && + echo rezrov >rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' cat >expected <nitfol && - git update-index --add nitfol && - read_tree_twoway $treeH $treeM && - git ls-files --stage >14.out && - test_must_fail git diff --no-index M.out 14.out >14diff.out && - compare_change 14diff.out expected && - check_cache_at nitfol clean' - -test_expect_success \ - '15 - unchanged in two heads.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo nitfol nitfol >nitfol && - git update-index --add nitfol && - echo nitfol nitfol nitfol >nitfol && - read_tree_twoway $treeH $treeM && - git ls-files --stage >15.out && - test_must_fail git diff --no-index M.out 15.out >15diff.out && - compare_change 15diff.out expected && - check_cache_at nitfol dirty' - -test_expect_success \ - '16 - conflicting local change.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo bozbar bozbar >bozbar && - git update-index --add bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '17 - conflicting local change.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo bozbar bozbar >bozbar && - git update-index --add bozbar && - echo bozbar bozbar bozbar >bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '18 - local change already having a good result.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-new >bozbar && - git update-index --add bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >18.out && - test_cmp M.out 18.out && - check_cache_at bozbar clean' - -test_expect_success \ - '19 - local change already having a good result, further modified.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-new >bozbar && - git update-index --add bozbar && - echo gnusto gnusto >bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >19.out && - test_cmp M.out 19.out && - check_cache_at bozbar dirty' - -test_expect_success \ - '20 - no local change, use new tree.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-old >bozbar && - git update-index --add bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >20.out && - test_cmp M.out 20.out && - check_cache_at bozbar dirty' - -test_expect_success \ - '21 - no local change, dirty cache.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-old >bozbar && - git update-index --add bozbar && - echo gnusto gnusto >bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '14 - unchanged in two heads.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo nitfol nitfol >nitfol && + git update-index --add nitfol && + read_tree_twoway $treeH $treeM && + git ls-files --stage >14.out && + test_must_fail git diff --no-index M.out 14.out >14diff.out && + compare_change 14diff.out expected && + check_cache_at nitfol clean +' + +test_expect_success '15 - unchanged in two heads.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo nitfol nitfol >nitfol && + git update-index --add nitfol && + echo nitfol nitfol nitfol >nitfol && + read_tree_twoway $treeH $treeM && + git ls-files --stage >15.out && + test_must_fail git diff --no-index M.out 15.out >15diff.out && + compare_change 15diff.out expected && + check_cache_at nitfol dirty +' + +test_expect_success '16 - conflicting local change.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo bozbar bozbar >bozbar && + git update-index --add bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '17 - conflicting local change.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo bozbar bozbar >bozbar && + git update-index --add bozbar && + echo bozbar bozbar bozbar >bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '18 - local change already having a good result.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-new >bozbar && + git update-index --add bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >18.out && + test_cmp M.out 18.out && + check_cache_at bozbar clean +' + +test_expect_success '19 - local change already having a good result, further modified.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-new >bozbar && + git update-index --add bozbar && + echo gnusto gnusto >bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >19.out && + test_cmp M.out 19.out && + check_cache_at bozbar dirty +' + +test_expect_success '20 - no local change, use new tree.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-old >bozbar && + git update-index --add bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >20.out && + test_cmp M.out 20.out && + check_cache_at bozbar dirty +' + +test_expect_success '21 - no local change, dirty cache.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-old >bozbar && + git update-index --add bozbar && + echo gnusto gnusto >bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' # This fails with straight two-way fast-forward. -test_expect_success \ - '22 - local change cache updated.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && - git update-index --add bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '22 - local change cache updated.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && + git update-index --add bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' # Also make sure we did not break DF vs DF/DF case. -test_expect_success \ - 'DF vs DF/DF case setup.' \ - 'rm -f .git/index && - echo DF >DF && - git update-index --add DF && - treeDF=$(git write-tree) && - echo treeDF $treeDF && - git ls-tree $treeDF && - - rm -f DF && - mkdir DF && - echo DF/DF >DF/DF && - git update-index --add --remove DF DF/DF && - treeDFDF=$(git write-tree) && - echo treeDFDF $treeDFDF && - git ls-tree $treeDFDF && - git ls-files --stage >DFDF.out' - -test_expect_success \ - 'DF vs DF/DF case test.' \ - 'rm -f .git/index && - rm -fr DF && - echo DF >DF && - git update-index --add DF && - read_tree_twoway $treeDF $treeDFDF && - git ls-files --stage >DFDFcheck.out && - test_cmp DFDF.out DFDFcheck.out && - check_cache_at DF/DF dirty && - :' - -test_expect_success \ - 'a/b (untracked) vs a case setup.' \ - 'rm -f .git/index && - : >a && - git update-index --add a && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git ls-files --stage >treeM.out && - - rm -f a && - git update-index --remove a && - mkdir a && - : >a/b && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH' - -test_expect_success \ - 'a/b (untracked) vs a, plus c/d case test.' \ - 'read_tree_u_must_fail -u -m "$treeH" "$treeM" && - git ls-files --stage && - test -f a/b' - -test_expect_success \ - 'a/b vs a, plus c/d case setup.' \ - 'rm -f .git/index && - rm -fr a && - : >a && - mkdir c && - : >c/d && - git update-index --add a c/d && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git ls-files --stage >treeM.out && - - rm -f a && - mkdir a && - : >a/b && - git update-index --add --remove a a/b && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH' - -test_expect_success \ - 'a/b vs a, plus c/d case test.' \ - 'read_tree_u_must_succeed -u -m "$treeH" "$treeM" && - git ls-files --stage | tee >treeMcheck.out && - test_cmp treeM.out treeMcheck.out' +test_expect_success 'DF vs DF/DF case setup.' ' + rm -f .git/index && + echo DF >DF && + git update-index --add DF && + treeDF=$(git write-tree) && + echo treeDF $treeDF && + git ls-tree $treeDF && + + rm -f DF && + mkdir DF && + echo DF/DF >DF/DF && + git update-index --add --remove DF DF/DF && + treeDFDF=$(git write-tree) && + echo treeDFDF $treeDFDF && + git ls-tree $treeDFDF && + git ls-files --stage >DFDF.out +' + +test_expect_success 'DF vs DF/DF case test.' ' + rm -f .git/index && + rm -fr DF && + echo DF >DF && + git update-index --add DF && + read_tree_twoway $treeDF $treeDFDF && + git ls-files --stage >DFDFcheck.out && + test_cmp DFDF.out DFDFcheck.out && + check_cache_at DF/DF dirty && + : +' + +test_expect_success 'a/b (untracked) vs a case setup.' ' + rm -f .git/index && + : >a && + git update-index --add a && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + git update-index --remove a && + mkdir a && + : >a/b && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH +' + +test_expect_success 'a/b (untracked) vs a, plus c/d case test.' ' + read_tree_u_must_fail -u -m "$treeH" "$treeM" && + git ls-files --stage && + test -f a/b +' + +test_expect_success 'a/b vs a, plus c/d case setup.' ' + rm -f .git/index && + rm -fr a && + : >a && + mkdir c && + : >c/d && + git update-index --add a c/d && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + mkdir a && + : >a/b && + git update-index --add --remove a a/b && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH +' + +test_expect_success 'a/b vs a, plus c/d case test.' ' + read_tree_u_must_succeed -u -m "$treeH" "$treeM" && + git ls-files --stage | tee >treeMcheck.out && + test_cmp treeM.out treeMcheck.out +' test_expect_success '-m references the correct modified tree' ' echo >file-a && From 875425080df13933baf484b35988d3a2dc04629e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 10 Jan 2017 12:06:10 -0800 Subject: [PATCH 0225/1540] index: improve constness for reading blob data Improve constness of the index_state parameter to the 'read_blob_data_from_index' function. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- cache.h | 2 +- read-cache.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cache.h b/cache.h index 1b67f078ddf747..ef89eb4a0f07ce 100644 --- a/cache.h +++ b/cache.h @@ -599,7 +599,7 @@ extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b); extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce); extern int index_name_is_other(const struct index_state *, const char *, int); -extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *); +extern void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *); /* do stat comparison even if CE_VALID is true */ #define CE_MATCH_IGNORE_VALID 01 diff --git a/read-cache.c b/read-cache.c index 2eca639cce3a08..7a9a7de91e6e84 100644 --- a/read-cache.c +++ b/read-cache.c @@ -2285,7 +2285,8 @@ int index_name_is_other(const struct index_state *istate, const char *name, return 1; } -void *read_blob_data_from_index(struct index_state *istate, const char *path, unsigned long *size) +void *read_blob_data_from_index(const struct index_state *istate, + const char *path, unsigned long *size) { int pos, len; unsigned long sz; From b5a9e435c6dfb40df0a27521c1c6590c8f68ffb2 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 11 Jan 2017 09:02:03 -0500 Subject: [PATCH 0226/1540] Revert "vreportf: avoid intermediate buffer" This reverts commit f4c3edc0b156362a92bf9de4f0ec794e90a757fc. The purpose of that commit was to let us write errors of arbitrary length to stderr by skipping the intermediate buffer and sending our varargs straight to fprintf. That works, but it comes with a downside: we do not get access to the varargs before they are sent to stderr. On balance, it's not a good tradeoff. Error messages larger than our 4K buffer are quite uncommon, and we've lost the ability to make any modifications to the output (e.g., to remove non-printable characters). The only way to have both would be one of: 1. Write into a dynamic buffer. But this is a bad idea for a low-level function that may be called when malloc() has failed. 2. Do our own printf-format varargs parsing. This is too complex to be worth the trouble. Let's just revert that change and go back to a fixed buffer. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- usage.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/usage.c b/usage.c index 82ff13163b542a..e4fa6d2f0315c6 100644 --- a/usage.c +++ b/usage.c @@ -7,21 +7,13 @@ #include "cache.h" static FILE *error_handle; -static int tweaked_error_buffering; void vreportf(const char *prefix, const char *err, va_list params) { + char msg[4096]; FILE *fh = error_handle ? error_handle : stderr; - - fflush(fh); - if (!tweaked_error_buffering) { - setvbuf(fh, NULL, _IOLBF, 0); - tweaked_error_buffering = 1; - } - - fputs(prefix, fh); - vfprintf(fh, err, params); - fputc('\n', fh); + vsnprintf(msg, sizeof(msg), err, params); + fprintf(fh, "%s%s\n", prefix, msg); } static NORETURN void usage_builtin(const char *err, va_list params) @@ -78,7 +70,6 @@ void set_die_is_recursing_routine(int (*routine)(void)) void set_error_handle(FILE *fh) { error_handle = fh; - tweaked_error_buffering = 0; } void NORETURN usagef(const char *err, ...) From f290089879501a855df2eb41db5b38cb0035a765 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 11 Jan 2017 09:02:23 -0500 Subject: [PATCH 0227/1540] vreport: sanitize ASCII control chars Our error() and die() calls may report messages with arbitrary data (e.g., filenames or even data from a remote server). Let's make it harder to cause confusion with mischievous filenames. E.g., try: git rev-parse "$(printf "\rfatal: this argument is too sneaky")" -- or git rev-parse "$(printf "\x1b[5mblinky\x1b[0m")" -- Let's block all ASCII control characters, with the exception of TAB and LF. We use both in our own messages (and we are necessarily sanitizing the complete output of snprintf here, as we do not have access to the individual varargs). And TAB and LF are unlikely to cause confusion (you could put "\nfatal: sneaky\n" in your filename, but it would at least not _cover up_ the message leading to it, unlike "\r"). We'll replace the characters with a "?", which is similar to how "ls" behaves. It might be nice to do something less lossy, like converting them to "\x" hex codes. But replacing with a single character makes it easy to do in-place and without worrying about length limitations. This feature should kick in rarely enough that the "?" marks are almost never seen. We'll leave high-bit characters as-is, as they are likely to be UTF-8 (though there may be some Unicode mischief you could cause, which may require further patches). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- usage.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/usage.c b/usage.c index e4fa6d2f0315c6..50a6ccee44cd79 100644 --- a/usage.c +++ b/usage.c @@ -12,7 +12,13 @@ void vreportf(const char *prefix, const char *err, va_list params) { char msg[4096]; FILE *fh = error_handle ? error_handle : stderr; + char *p; + vsnprintf(msg, sizeof(msg), err, params); + for (p = msg; *p; p++) { + if (iscntrl(*p) && *p != '\t' && *p != '\n') + *p = '?'; + } fprintf(fh, "%s%s\n", prefix, msg); } From b17846432da4f8530c7349561eac4a16f95bbd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:48:11 +0100 Subject: [PATCH 0228/1540] versioncmp: factor out helper for suffix matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As the number of identical steps to be done for both tagnames grows, extract them into a helper function, with the additional benefit that the conditionals near the end of swap_prereleases() will use more meaningful variable names. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- versioncmp.c | 64 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/versioncmp.c b/versioncmp.c index ae0a9199bd3b6d..4cb400f90150af 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -24,6 +24,31 @@ static const struct string_list *prereleases; static int initialized; +struct suffix_match { + int conf_pos; + int start; + int len; +}; + +static void find_better_matching_suffix(const char *tagname, const char *suffix, + int suffix_len, int start, int conf_pos, + struct suffix_match *match) +{ + /* + * A better match either starts earlier or starts at the same offset + * but is longer. + */ + int end = match->len < suffix_len ? match->start : match->start-1; + int i; + for (i = start; i <= end; i++) + if (starts_with(tagname + i, suffix)) { + match->conf_pos = conf_pos; + match->start = i; + match->len = suffix_len; + break; + } +} + /* * off is the offset of the first different character in the two strings * s1 and s2. If either s1 or s2 contains a prerelease suffix containing @@ -47,46 +72,35 @@ static int swap_prereleases(const char *s1, int off, int *diff) { - int i, i1 = -1, i2 = -1; - int start_at1 = off, start_at2 = off, match_len1 = -1, match_len2 = -1; + int i; + struct suffix_match match1 = { -1, off, -1 }; + struct suffix_match match2 = { -1, off, -1 }; for (i = 0; i < prereleases->nr; i++) { const char *suffix = prereleases->items[i].string; - int j, start, end, suffix_len = strlen(suffix); + int start, suffix_len = strlen(suffix); if (suffix_len < off) start = off - suffix_len; else start = 0; - end = match_len1 < suffix_len ? start_at1 : start_at1-1; - for (j = start; j <= end; j++) - if (starts_with(s1 + j, suffix)) { - i1 = i; - start_at1 = j; - match_len1 = suffix_len; - break; - } - end = match_len2 < suffix_len ? start_at2 : start_at2-1; - for (j = start; j <= end; j++) - if (starts_with(s2 + j, suffix)) { - i2 = i; - start_at2 = j; - match_len2 = suffix_len; - break; - } + find_better_matching_suffix(s1, suffix, suffix_len, start, + i, &match1); + find_better_matching_suffix(s2, suffix, suffix_len, start, + i, &match2); } - if (i1 == -1 && i2 == -1) + if (match1.conf_pos == -1 && match2.conf_pos == -1) return 0; - if (i1 == i2) + if (match1.conf_pos == match2.conf_pos) /* Found the same suffix in both, e.g. "-rc" in "v1.0-rcX" * and "v1.0-rcY": the caller should decide based on "X" * and "Y". */ return 0; - if (i1 >= 0 && i2 >= 0) - *diff = i1 - i2; - else if (i1 >= 0) + if (match1.conf_pos >= 0 && match2.conf_pos >= 0) + *diff = match1.conf_pos - match2.conf_pos; + else if (match1.conf_pos >= 0) *diff = -1; - else /* if (i2 >= 0) */ + else /* if (match2.conf_pos >= 0) */ *diff = 1; return 1; } From c026557a37361b7019acca28f240a19f546739e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 8 Dec 2016 15:24:01 +0100 Subject: [PATCH 0229/1540] versioncmp: generalize version sort suffix reordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'versionsort.prereleaseSuffix' configuration variable, as its name suggests, is supposed to only deal with tagnames with prerelease suffixes, and allows sorting those prerelease tags in a user-defined order before the suffixless main release tag, instead of sorting them simply lexicographically. However, the previous changes in this series resulted in an interesting and useful property of version sort: - The empty string as a configured suffix matches all tagnames, including tagnames without any suffix, but - tagnames containing a "real" configured suffix are still ordered according to that real suffix, because any longer suffix takes precedence over the empty string. Exploiting this property we can easily generalize suffix reordering and specify the order of tags with given suffixes not only before but even after a main release tag by using the empty suffix to denote the position of the main release tag, without any algorithm changes: $ git -c versionsort.prereleaseSuffix=-alpha \ -c versionsort.prereleaseSuffix=-beta \ -c versionsort.prereleaseSuffix="" \ -c versionsort.prereleaseSuffix=-gamma \ -c versionsort.prereleaseSuffix=-delta \ tag -l --sort=version:refname 'v3.0*' v3.0-alpha1 v3.0-beta1 v3.0 v3.0-gamma1 v3.0-delta1 Since 'versionsort.prereleaseSuffix' is not a fitting name for a configuration variable to control this more general suffix reordering, introduce the new variable 'versionsort.suffix'. Still keep the old configuration variable name as a deprecated alias, though, to avoid suddenly breaking setups already using it. Ignore the old variable if both old and new configuration variables are set, but emit a warning so users will be aware of it and can fix their configuration. Extend the documentation to describe and add a test to check this more general behavior. Note: since the empty suffix matches all tagnames, tagnames with suffixes not included in the configuration are listed together with the suffixless main release tag, ordered lexicographically right after that, i.e. before tags with suffixes listed in the configuration following the empty suffix. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- Documentation/config.txt | 36 ++++++++++++++++++++++++++---------- Documentation/git-tag.txt | 4 ++-- t/t7004-tag.sh | 35 +++++++++++++++++++++++++++++++++++ versioncmp.c | 9 ++++++++- 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 9078c8c4f4db14..0573f1f8665702 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -3038,16 +3038,32 @@ user.signingKey:: This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. -versionsort.prereleaseSuffix:: - When version sort is used in linkgit:git-tag[1], prerelease - tags (e.g. "1.0-rc1") may appear after the main release - "1.0". By specifying the suffix "-rc" in this variable, - "1.0-rc1" will appear before "1.0". -+ -This variable can be specified multiple times, once per suffix. The -order of suffixes in the config file determines the sorting order -(e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX -is sorted before 1.0-rcXX). +versionsort.prereleaseSuffix (deprecated):: + Deprecated alias for `versionsort.suffix`. Ignored if + `versionsort.suffix` is set. + +versionsort.suffix:: + Even when version sort is used in linkgit:git-tag[1], tagnames + with the same base version but different suffixes are still sorted + lexicographically, resulting e.g. in prerelease tags appearing + after the main release (e.g. "1.0-rc1" after "1.0"). This + variable can be specified to determine the sorting order of tags + with different suffixes. ++ +By specifying a single suffix in this variable, any tagname containing +that suffix will appear before the corresponding main release. E.g. if +the variable is set to "-rc", then all "1.0-rcX" tags will appear before +"1.0". If specified multiple times, once per suffix, then the order of +suffixes in the configuration will determine the sorting order of tagnames +with those suffixes. E.g. if "-pre" appears before "-rc" in the +configuration, then all "1.0-preX" tags will be listed before any +"1.0-rcX" tags. The placement of the main release tag relative to tags +with various suffixes can be determined by specifying the empty suffix +among those other suffixes. E.g. if the suffixes "-rc", "", "-ck" and +"-bfs" appear in the configuration in this order, then all "v4.8-rcX" tags +are listed first, followed by "v4.8", then "v4.8-ckX" and finally +"v4.8-bfsX". ++ If more than one suffixes match the same tagname, then that tagname will be sorted according to the suffix which starts at the earliest position in the tagname. If more than one different matching suffixes start at diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 80019c584b11b3..44c956c67bb490 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -101,8 +101,8 @@ OPTIONS 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. + order can also be affected by the "versionsort.suffix" + 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 diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index e2efe312dcf0ac..bdd28dad14d6f6 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1595,6 +1595,41 @@ test_expect_success 'version sort with prerelease reordering, multiple suffixes test_cmp expect actual ' +test_expect_success 'version sort with general suffix reordering' ' + test_config versionsort.suffix -alpha && + git config --add versionsort.suffix -beta && + git config --add versionsort.suffix "" && + git config --add versionsort.suffix -gamma && + git config --add versionsort.suffix -delta && + git tag foo1.10-alpha && + git tag foo1.10-beta && + git tag foo1.10-gamma && + git tag foo1.10-delta && + git tag foo1.10-unlisted-suffix && + git tag -l --sort=version:refname "foo1.10*" >actual && + cat >expect <<-\EOF && + foo1.10-alpha + foo1.10-beta + foo1.10 + foo1.10-unlisted-suffix + foo1.10-gamma + foo1.10-delta + EOF + test_cmp expect actual +' + +test_expect_success 'versionsort.suffix overrides versionsort.prereleaseSuffix' ' + test_config versionsort.suffix -before && + test_config versionsort.prereleaseSuffix -after && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7 + foo1.7-after1 + EOF + test_cmp expect actual +' + test_expect_success 'version sort with very long prerelease suffix' ' test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix && git tag -l --sort=version:refname diff --git a/versioncmp.c b/versioncmp.c index 4cb400f90150af..9f81dc1062bc1f 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -159,8 +159,15 @@ int versioncmp(const char *s1, const char *s2) } if (!initialized) { + const struct string_list *deprecated_prereleases; initialized = 1; - prereleases = git_config_get_value_multi("versionsort.prereleasesuffix"); + prereleases = git_config_get_value_multi("versionsort.suffix"); + deprecated_prereleases = git_config_get_value_multi("versionsort.prereleasesuffix"); + if (prereleases) { + if (deprecated_prereleases) + warning("ignoring versionsort.prereleasesuffix because versionsort.suffix is set"); + } else + prereleases = deprecated_prereleases; } if (prereleases && swap_prereleases(s1, s2, (const char *) p1 - s1 - 1, &diff)) From c488867793dc9b273c1d35746452d44afcd3d7f5 Mon Sep 17 00:00:00 2001 From: Vegard Nossum Date: Thu, 12 Jan 2017 13:21:11 +0100 Subject: [PATCH 0230/1540] diff: add interhunk context config option The --inter-hunk-context= option was added in commit 6d0e674a5754 ("diff: add option to show context between close hunks"). This patch allows configuring a default for this option. Signed-off-by: Vegard Nossum Signed-off-by: Junio C Hamano --- Documentation/diff-config.txt | 6 ++++++ Documentation/diff-options.txt | 2 ++ diff.c | 8 ++++++++ t/t4032-diff-inter-hunk-context.sh | 27 ++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index d8570f2a75096c..15521f5191b6fd 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -60,6 +60,12 @@ diff.context:: Generate diffs with lines of context instead of the default of 3. This value is overridden by the -U option. +diff.interHunkContext:: + Show the context between diff hunks, up to the specified number + of lines, thereby fusing the hunks that are close to each other. + This value serves as the default for the `--inter-hunk-context` + command line option. + diff.external:: If this config variable is set, diff generation is not performed using the internal diff machinery, but using the diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index e6215c372c3c3c..a219aa290726d5 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -511,6 +511,8 @@ endif::git-format-patch[] --inter-hunk-context=:: Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. + Defaults to `diff.interHunkContext` or 0 if the config option + is unset. -W:: --function-context:: diff --git a/diff.c b/diff.c index e2eb6d66a9a633..f08cd8e033d104 100644 --- a/diff.c +++ b/diff.c @@ -32,6 +32,7 @@ static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; static int diff_context_default = 3; +static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; static const char *diff_order_file_cfg; @@ -239,6 +240,12 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return -1; return 0; } + if (!strcmp(var, "diff.interhunkcontext")) { + diff_interhunk_context_default = git_config_int(var, value); + if (diff_interhunk_context_default < 0) + return -1; + return 0; + } if (!strcmp(var, "diff.renames")) { diff_detect_rename_default = git_config_rename(var, value); return 0; @@ -3362,6 +3369,7 @@ void diff_setup(struct diff_options *options) options->rename_limit = -1; options->dirstat_permille = diff_dirstat_permille_default; options->context = diff_context_default; + options->interhunkcontext = diff_interhunk_context_default; options->ws_error_highlight = ws_error_highlight_default; DIFF_OPT_SET(options, RENAME_EMPTY); diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh index e4e3e28fc788fd..bada0cbd32f764 100755 --- a/t/t4032-diff-inter-hunk-context.sh +++ b/t/t4032-diff-inter-hunk-context.sh @@ -16,11 +16,15 @@ f() { } t() { + use_config= + git config --unset diff.interHunkContext + case $# in 4) hunks=$4; cmd="diff -U$3";; 5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";; + 6) hunks=$5; cmd="diff -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";; esac - label="$cmd, $1 common $2" + label="$use_config$cmd, $1 common $2" file=f$1 expected=expected.$file.$3.$hunks @@ -89,4 +93,25 @@ t 9 lines 3 2 t 9 lines 3 2 2 t 9 lines 3 3 1 +# use diff.interHunkContext? +t 1 line 0 0 2 config +t 1 line 0 1 1 config +t 1 line 0 2 1 config +t 9 lines 3 3 1 config +t 2 lines 0 0 2 config +t 2 lines 0 1 2 config +t 2 lines 0 2 1 config +t 3 lines 1 0 2 config +t 3 lines 1 1 1 config +t 3 lines 1 2 1 config +t 9 lines 3 2 2 config +t 9 lines 3 3 1 config + +test_expect_success 'diff.interHunkContext invalid' ' + git config diff.interHunkContext asdf && + test_must_fail git diff && + git config diff.interHunkContext -1 && + test_must_fail git diff +' + test_done From c32eaa8af17fb295fe5a2b18fec0ba4c363c9db6 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Wed, 11 Jan 2017 12:59:17 -0800 Subject: [PATCH 0231/1540] submodule absorbgitdirs: mention in docstring help This part was missing in f6f85861 (submodule: add absorb-git-dir function, 2016-12-12). Noticed-by: Jonathan Nieder Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- git-submodule.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-submodule.sh b/git-submodule.sh index 9285b5c43d3658..56bb3737e51c6d 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -12,7 +12,8 @@ USAGE="[--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--recursive] [--] [...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit ] [commit] [--] [...] or: $dashless [--quiet] foreach [--recursive] - or: $dashless [--quiet] sync [--recursive] [--] [...]" + or: $dashless [--quiet] sync [--recursive] [--] [...] + or: $dashless [--quiet] absorbgitdirs [--] [...]" OPTIONS_SPEC= SUBDIRECTORY_OK=Yes . git-sh-setup From 7af55d1f2bfafbe0fbc897e6f82a36d1e460b680 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 27 Dec 2016 11:36:04 -0800 Subject: [PATCH 0232/1540] t7411: quote URLs The variables may contain white spaces, so we need to quote them. By not quoting the variables we'd end up passing multiple arguments to git config, which doesn't fail for two arguments as value. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- t/t7411-submodule-config.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index d389ae5408ab78..e4bb9851b13ba3 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -134,8 +134,8 @@ test_expect_success 'reading of local configuration' ' "" submodule \ >actual && test_cmp expect_local_path actual && - git config submodule.a.url $old_a && - git config submodule.submodule.url $old_submodule && + git config submodule.a.url "$old_a" && + git config submodule.submodule.url "$old_submodule" && git config --unset submodule.a.path c ) ' From 239039bd703e9f2286699de5aedae4c17defbbf1 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 27 Dec 2016 11:36:05 -0800 Subject: [PATCH 0233/1540] t7411: test lookup of uninitialized submodules Sometimes we need to lookup information of uninitialized submodules. Make sure that works. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- t/t7411-submodule-config.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index e4bb9851b13ba3..eea36f1dbe3785 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -140,6 +140,27 @@ test_expect_success 'reading of local configuration' ' ) ' +cat >super/expect_url <actual && + test_cmp expect_url actual && + git config submodule.submodule.url "$old_submodule" && + git submodule init b + ) +' + cat >super/expect_fetchrecurse_die.err < Date: Tue, 27 Dec 2016 15:43:08 -0800 Subject: [PATCH 0234/1540] submodule documentation: add options to the subcommand When reading up on a subcommand of `git submodule `, it is convenient to have its options nearby and not just at the top of the man page. Add the options to each subcommand. While at it, also document the `--checkout` option for `update`. Signed-off-by: Stefan Beller Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 918bd1d1bd062a..0105bdbe1c9da3 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,17 +9,12 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b ] [-f|--force] [--name ] - [--reference ] [--depth ] [--] [] +'git submodule' [--quiet] add [] [--] [] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [...] 'git submodule' [--quiet] init [--] [...] 'git submodule' [--quiet] deinit [-f|--force] (--all|[--] ...) -'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] - [--[no-]recommend-shallow] [-f|--force] [--rebase|--merge] - [--reference ] [--depth ] [--recursive] - [--jobs ] [--] [...] -'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) ] - [commit] [--] [...] +'git submodule' [--quiet] update [] [--] [...] +'git submodule' [--quiet] summary [] [--] [...] 'git submodule' [--quiet] foreach [--recursive] 'git submodule' [--quiet] sync [--recursive] [--] [...] 'git submodule' [--quiet] absorbgitdirs [--] [...] @@ -63,7 +58,7 @@ if you choose to go that route. COMMANDS -------- -add:: +add [-b ] [-f|--force] [--name ] [--reference ] [--depth ] [--] []:: Add the given repository as a submodule at the given path to the changeset to be committed next to the current project: the current project is termed the "superproject". @@ -104,7 +99,7 @@ together in the same relative location, and only the superproject's URL needs to be provided: git-submodule will correctly locate the submodule using the relative URL in .gitmodules. -status:: +status [--cached] [--recursive] [--] [...]:: Show the status of the submodules. This will print the SHA-1 of the currently checked out commit for each submodule, along with the submodule path and the output of 'git describe' for the @@ -121,7 +116,7 @@ submodules with respect to the commit recorded in the index or the HEAD, linkgit:git-status[1] and linkgit:git-diff[1] will provide that information too (and can also report changes to a submodule's work tree). -init:: +init [--] [...]:: Initialize the submodules recorded in the index (which were added and committed elsewhere) by copying submodule names and urls from .gitmodules to .git/config. @@ -136,7 +131,7 @@ init:: the explicit 'init' step if you do not intend to customize any submodule locations. -deinit:: +deinit [-f|--force] (--all|[--] ...):: Unregister the given submodules, i.e. remove the whole `submodule.$name` section from .git/config together with their work tree. Further calls to `git submodule update`, `git submodule foreach` @@ -152,7 +147,7 @@ instead of deinit-ing everything, to prevent mistakes. If `--force` is specified, the submodule's working tree will be removed even if it contains local modifications. -update:: +update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference ] [--depth ] [--recursive] [--jobs ] [--] [...]:: + -- Update the registered submodules to match what the superproject @@ -198,7 +193,7 @@ submodule with the `--init` option. If `--recursive` is specified, this command will recurse into the registered submodules, and update any nested submodules within. -- -summary:: +summary [--cached|--files] [(-n|--summary-limit) ] [commit] [--] [...]:: Show commit summary between the given commit (defaults to HEAD) and working tree/index. For a submodule in question, a series of commits in the submodule between the given super project commit and the @@ -211,7 +206,7 @@ summary:: Using the `--submodule=log` option with linkgit:git-diff[1] will provide that information too. -foreach:: +foreach [--recursive] :: Evaluates an arbitrary shell command in each checked out submodule. The command has access to the variables $name, $path, $sha1 and $toplevel: @@ -232,7 +227,7 @@ As an example, +git submodule foreach \'echo $path {backtick}git rev-parse HEAD{backtick}'+ will show the path and currently checked out commit for each submodule. -sync:: +sync [--recursive] [--] [...]:: Synchronizes submodules' remote URL configuration setting to the value specified in .gitmodules. It will only affect those submodules which already have a URL entry in .git/config (that is the From fc01a5d26a8f0f80db8302d3ef3ce10924a68581 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Tue, 27 Dec 2016 15:43:09 -0800 Subject: [PATCH 0235/1540] submodule update documentation: don't repeat ourselves The documentation for the `git submodule update` command, repeats itself for each update option, "This is done when