diff options
-rw-r--r-- | Documentation/config.txt | 4 | ||||
-rw-r--r-- | Documentation/git-am.txt | 2 | ||||
-rw-r--r-- | Documentation/git-bundle.txt | 44 | ||||
-rw-r--r-- | Documentation/git-filter-branch.txt | 15 | ||||
-rw-r--r-- | Documentation/git-rev-list.txt | 1 | ||||
-rw-r--r-- | Documentation/rev-list-options.txt | 5 | ||||
-rw-r--r-- | builtin-apply.c | 719 | ||||
-rwxr-xr-x | builtin-fast-export.c | 2 | ||||
-rw-r--r-- | builtin-for-each-ref.c | 2 | ||||
-rw-r--r-- | builtin-init-db.c | 21 | ||||
-rw-r--r-- | builtin-rerere.c | 19 | ||||
-rw-r--r-- | builtin-rev-parse.c | 2 | ||||
-rw-r--r-- | cache.h | 3 | ||||
-rw-r--r-- | copy.c | 21 | ||||
-rw-r--r-- | date.c | 6 | ||||
-rw-r--r-- | diff.c | 103 | ||||
-rw-r--r-- | diff.h | 3 | ||||
-rwxr-xr-x | gitweb/gitweb.perl | 73 | ||||
-rw-r--r-- | revision.c | 10 | ||||
-rwxr-xr-x | t/t0050-filesystem.sh | 93 | ||||
-rwxr-xr-x | t/t1502-rev-parse-parseopt.sh | 43 | ||||
-rwxr-xr-x | t/t4019-diff-wserror.sh | 40 | ||||
-rwxr-xr-x | t/t4105-apply-fuzz.sh | 57 | ||||
-rwxr-xr-x | t/t4125-apply-ws-fuzz.sh | 103 | ||||
-rwxr-xr-x | t/t9001-send-email.sh | 51 | ||||
-rw-r--r-- | ws.c | 119 |
26 files changed, 1230 insertions, 331 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 7b67671..fb6dae0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -353,6 +353,10 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `cr-at-eol` treats a carriage-return at the end of line as + part of the line terminator, i.e. with it, `trailing-space` + does not trigger if the character before such a carriage-return + is not a whitespace (not enabled by default). alias.*:: Command aliases for the linkgit:git[1] command wrapper - e.g. diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 2ffba21..e640fc7 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -138,7 +138,7 @@ aborts in the middle,. You can recover from this in one of two ways: The command refuses to process new mailboxes while `.dotest` directory exists, so if you decide to start over from scratch, -run `rm -f .dotest` before running the command with mailbox +run `rm -f -r .dotest` before running the command with mailbox names. diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 72f080a..505ac05 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -99,36 +99,62 @@ Assume two repositories exist as R1 on machine A, and R2 on machine B. For whatever reason, direct connection between A and B is not allowed, but we can move data from A to B via some mechanism (CD, email, etc). We want to update R2 with developments made on branch master in R1. + +To create the bundle you have to specify the basis. You have some options: + +- Without basis. ++ +This is useful when sending the whole history. + +------------ +$ git bundle create mybundle master +------------ + +- Using temporally tags. ++ We set a tag in R1 (lastR2bundle) after the previous such transport, and move it afterwards to help build the bundle. -in R1 on A: - ------------ $ git-bundle create mybundle master ^lastR2bundle $ git tag -f lastR2bundle master ------------ -(move mybundle from A to B by some mechanism) +- Using a tag present in both repositories + +------------ +$ git bundle create mybundle master ^v1.0.0 +------------ + +- A basis based on time. + +------------ +$ git bundle create mybundle master --since=10.days.ago +------------ -in R2 on B: +- With a limit on the number of commits ------------ -$ git-bundle verify mybundle -$ git-fetch mybundle refspec +$ git bundle create mybundle master -n 10 ------------ -where refspec is refInBundle:localRef +Then you move mybundle from A to B, and in R2 on B: +------------ +$ git-bundle verify mybundle +$ git-fetch mybundle master:localRef +------------ -Also, with something like this in your config: +With something like this in the config in R2: +------------------------ [remote "bundle"] url = /home/me/tmp/file.bdl fetch = refs/heads/*:refs/remotes/origin/* +------------------------ You can first sneakernet the bundle file to ~/tmp/file.bdl and -then these commands: +then these commands on machine B: ------------ $ git ls-remote bundle diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index e22dfa5..543a1cf 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -56,7 +56,9 @@ notable exception of the commit filter, for technical reasons). Prior to that, the $GIT_COMMIT environment variable will be set to contain the id of the commit being rewritten. Also, GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, -and GIT_COMMITTER_DATE are set according to the current commit. +and GIT_COMMITTER_DATE are set according to the current commit. If any +evaluation of <command> returns a non-zero exit status, the whole operation +will be aborted. A 'map' function is available that takes an "original sha1 id" argument and outputs a "rewritten sha1 id" if the commit has been already @@ -197,7 +199,7 @@ happened). If this is not the case, use: -------------------------------------------------------------------------- git filter-branch --parent-filter \ - 'cat; test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>"' HEAD + 'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD -------------------------------------------------------------------------- or even simpler: @@ -240,6 +242,15 @@ committed a merge between P1 and P2, it will be propagated properly and all children of the merge will become merge commits with P1,P2 as their parents instead of the merge commit. +You can rewrite the commit log messages using `--message-filter`. For +example, `git-svn-id` strings in a repository created by `git-svn` can +be removed this way: + +------------------------------------------------------- +git filter-branch --message-filter ' + sed -e "/^git-svn-id:/d" +' +------------------------------------------------------- To restrict rewriting to only part of the history, specify a revision range in addition to the new branch name. The new branch name will diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 5b96eab..a8d489f 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -31,6 +31,7 @@ SYNOPSIS [ \--(author|committer|grep)=<pattern> ] [ \--regexp-ignore-case | \-i ] [ \--extended-regexp | \-E ] + [ \--fixed-strings | \-F ] [ \--date={local|relative|default|iso|rfc|short} ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a8138e2..259072c 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -153,6 +153,11 @@ limiting may be applied. Consider the limiting patterns to be extended regular expressions instead of the default basic regular expressions. +-F, --fixed-strings:: + + Consider the limiting patterns to be fixed strings (don't interpret + pattern as a regular expression). + --remove-empty:: Stop when a given path disappears from the tree. diff --git a/builtin-apply.c b/builtin-apply.c index 6a88ff0..a3f075d 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -161,6 +161,84 @@ struct patch { struct patch *next; }; +/* + * A line in a file, len-bytes long (includes the terminating LF, + * except for an incomplete line at the end if the file ends with + * one), and its contents hashes to 'hash'. + */ +struct line { + size_t len; + unsigned hash : 24; + unsigned flag : 8; +#define LINE_COMMON 1 +}; + +/* + * This represents a "file", which is an array of "lines". + */ +struct image { + char *buf; + size_t len; + size_t nr; + size_t alloc; + struct line *line_allocated; + struct line *line; +}; + +static uint32_t hash_line(const char *cp, size_t len) +{ + size_t i; + uint32_t h; + for (i = 0, h = 0; i < len; i++) { + if (!isspace(cp[i])) { + h = h * 3 + (cp[i] & 0xff); + } + } + return h; +} + +static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) +{ + ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); + img->line_allocated[img->nr].len = len; + img->line_allocated[img->nr].hash = hash_line(bol, len); + img->line_allocated[img->nr].flag = flag; + img->nr++; +} + +static void prepare_image(struct image *image, char *buf, size_t len, + int prepare_linetable) +{ + const char *cp, *ep; + + memset(image, 0, sizeof(*image)); + image->buf = buf; + image->len = len; + + if (!prepare_linetable) + return; + + ep = image->buf + image->len; + cp = image->buf; + while (cp < ep) { + const char *next; + for (next = cp; next < ep && *next != '\n'; next++) + ; + if (next < ep) + next++; + add_line_info(image, cp, next - cp, 0); + cp = next; + } + image->line = image->line_allocated; +} + +static void clear_image(struct image *image) +{ + free(image->buf); + image->buf = NULL; + image->len = 0; +} + static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) { @@ -1437,227 +1515,338 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) } } -static int find_offset(const char *buf, unsigned long size, - const char *fragment, unsigned long fragsize, - int line, int *lines) +static void update_pre_post_images(struct image *preimage, + struct image *postimage, + char *buf, + size_t len) { - int i; - unsigned long start, backwards, forwards; + int i, ctx; + char *new, *old, *fixed; + struct image fixed_preimage; - if (fragsize > size) - return -1; + /* + * Update the preimage with whitespace fixes. Note that we + * are not losing preimage->buf -- apply_one_fragment() will + * free "oldlines". + */ + prepare_image(&fixed_preimage, buf, len, 1); + assert(fixed_preimage.nr == preimage->nr); + for (i = 0; i < preimage->nr; i++) + fixed_preimage.line[i].flag = preimage->line[i].flag; + free(preimage->line_allocated); + *preimage = fixed_preimage; - start = 0; - if (line > 1) { - unsigned long offset = 0; - i = line-1; - while (offset + fragsize <= size) { - if (buf[offset++] == '\n') { - start = offset; - if (!--i) - break; - } + /* + * Adjust the common context lines in postimage, in place. + * This is possible because whitespace fixing does not make + * the string grow. + */ + new = old = postimage->buf; + fixed = preimage->buf; + for (i = ctx = 0; i < postimage->nr; i++) { + size_t len = postimage->line[i].len; + if (!(postimage->line[i].flag & LINE_COMMON)) { + /* an added line -- no counterparts in preimage */ + memmove(new, old, len); + old += len; + new += len; + continue; } + + /* a common context -- skip it in the original postimage */ + old += len; + + /* and find the corresponding one in the fixed preimage */ + while (ctx < preimage->nr && + !(preimage->line[ctx].flag & LINE_COMMON)) { + fixed += preimage->line[ctx].len; + ctx++; + } + if (preimage->nr <= ctx) + die("oops"); + + /* and copy it in, while fixing the line length */ + len = preimage->line[ctx].len; + memcpy(new, fixed, len); + new += len; + fixed += len; + postimage->line[i].len = len; + ctx++; } - /* Exact line number? */ - if ((start + fragsize <= size) && - !memcmp(buf + start, fragment, fragsize)) - return start; + /* Fix the length of the whole thing */ + postimage->len = new - postimage->buf; +} + +static int match_fragment(struct image *img, + struct image *preimage, + struct image *postimage, + unsigned long try, + int try_lno, + unsigned ws_rule, + int match_beginning, int match_end) +{ + int i; + char *fixed_buf, *buf, *orig, *target; + + if (preimage->nr + try_lno > img->nr) + return 0; + + if (match_beginning && try_lno) + return 0; + + if (match_end && preimage->nr + try_lno != img->nr) + return 0; + + /* Quick hash check */ + for (i = 0; i < preimage->nr; i++) + if (preimage->line[i].hash != img->line[try_lno + i].hash) + return 0; + + /* + * Do we have an exact match? If we were told to match + * at the end, size must be exactly at try+fragsize, + * otherwise try+fragsize must be still within the preimage, + * and either case, the old piece should match the preimage + * exactly. + */ + if ((match_end + ? (try + preimage->len == img->len) + : (try + preimage->len <= img->len)) && + !memcmp(img->buf + try, preimage->buf, preimage->len)) + return 1; + + if (ws_error_action != correct_ws_error) + return 0; + + /* + * The hunk does not apply byte-by-byte, but the hash says + * it might with whitespace fuzz. + */ + fixed_buf = xmalloc(preimage->len + 1); + buf = fixed_buf; + orig = preimage->buf; + target = img->buf + try; + for (i = 0; i < preimage->nr; i++) { + size_t fixlen; /* length after fixing the preimage */ + size_t oldlen = preimage->line[i].len; + size_t tgtlen = img->line[try_lno + i].len; + size_t tgtfixlen; /* length after fixing the target line */ + char tgtfixbuf[1024], *tgtfix; + int match; + + /* Try fixing the line in the preimage */ + fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); + + /* Try fixing the line in the target */ + if (sizeof(tgtfixbuf) < tgtlen) + tgtfix = tgtfixbuf; + else + tgtfix = xmalloc(tgtlen); + tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL); + + /* + * If they match, either the preimage was based on + * a version before our tree fixed whitespace breakage, + * or we are lacking a whitespace-fix patch the tree + * the preimage was based on already had (i.e. target + * has whitespace breakage, the preimage doesn't). + * In either case, we are fixing the whitespace breakages + * so we might as well take the fix together with their + * real change. + */ + match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen)); + + if (tgtfix != tgtfixbuf) + free(tgtfix); + if (!match) + goto unmatch_exit; + + orig += oldlen; + buf += fixlen; + target += tgtlen; + } + + /* + * Yes, the preimage is based on an older version that still + * has whitespace breakages unfixed, and fixing them makes the + * hunk match. Update the context lines in the postimage. + */ + update_pre_post_images(preimage, postimage, + fixed_buf, buf - fixed_buf); + return 1; + + unmatch_exit: + free(fixed_buf); + return 0; +} + +static int find_pos(struct image *img, + struct image *preimage, + struct image *postimage, + int line, + unsigned ws_rule, + int match_beginning, int match_end) +{ + int i; + unsigned long backwards, forwards, try; + int backwards_lno, forwards_lno, try_lno; + + if (preimage->nr > img->nr) + return -1; + + /* + * If match_begining or match_end is specified, there is no + * point starting from a wrong line that will never match and + * wander around and wait for a match at the specified end. + */ + if (match_beginning) + line = 0; + else if (match_end) + line = img->nr - preimage->nr; + + if (line > img->nr) + line = img->nr; + + try = 0; + for (i = 0; i < line; i++) + try += img->line[i].len; /* * There's probably some smart way to do this, but I'll leave * that to the smart and beautiful people. I'm simple and stupid. */ - backwards = start; - forwards = start; + backwards = try; + backwards_lno = line; + forwards = try; + forwards_lno = line; + try_lno = line; + for (i = 0; ; i++) { - unsigned long try; - int n; + if (match_fragment(img, preimage, postimage, + try, try_lno, ws_rule, + match_beginning, match_end)) + return try_lno; + + again: + if (backwards_lno == 0 && forwards_lno == img->nr) + break; - /* "backward" */ if (i & 1) { - if (!backwards) { - if (forwards + fragsize > size) - break; - continue; + if (backwards_lno == 0) { + i++; + goto again; } - do { - --backwards; - } while (backwards && buf[backwards-1] != '\n'); + backwards_lno--; + backwards -= img->line[backwards_lno].len; try = backwards; + try_lno = backwards_lno; } else { - while (forwards + fragsize <= size) { - if (buf[forwards++] == '\n') - break; + if (forwards_lno == img->nr) { + i++; + goto again; } + forwards += img->line[forwards_lno].len; + forwards_lno++; try = forwards; + try_lno = forwards_lno; } - if (try + fragsize > size) - continue; - if (memcmp(buf + try, fragment, fragsize)) - continue; - n = (i >> 1)+1; - if (i & 1) - n = -n; - *lines = n; - return try; } - - /* - * We should start searching forward and backward. - */ return -1; } -static void remove_first_line(const char **rbuf, int *rsize) +static void remove_first_line(struct image *img) { - const char *buf = *rbuf; - int size = *rsize; - unsigned long offset; - offset = 0; - while (offset <= size) { - if (buf[offset++] == '\n') - break; - } - *rsize = size - offset; - *rbuf = buf + offset; + img->buf += img->line[0].len; + img->len -= img->line[0].len; + img->line++; + img->nr--; } -static void remove_last_line(const char **rbuf, int *rsize) +static void remove_last_line(struct image *img) { - const char *buf = *rbuf; - int size = *rsize; - unsigned long offset; - offset = size - 1; - while (offset > 0) { - if (buf[--offset] == '\n') - break; - } - *rsize = offset + 1; + img->len -= img->line[--img->nr].len; } -static int apply_line(char *output, const char *patch, int plen, - unsigned ws_rule) +static void update_image(struct image *img, + int applied_pos, + struct image *preimage, + struct image *postimage) { /* - * plen is number of bytes to be copied from patch, - * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n', unless this is the incomplete - * last line. + * remove the copy of preimage at offset in img + * and replace it with postimage */ - int i; - int add_nl_to_tail = 0; - int fixed = 0; - int last_tab_in_indent = 0; - int last_space_in_indent = 0; - int need_fix_leading_space = 0; - char *buf; - - if ((ws_error_action != correct_ws_error) || !whitespace_error || - *patch != '+') { - memcpy(output, patch + 1, plen); - return plen; - } - - /* - * Strip trailing whitespace - */ - if ((ws_rule & WS_TRAILING_SPACE) && - (1 < plen && isspace(patch[plen-1]))) { - if (patch[plen] == '\n') - add_nl_to_tail = 1; - plen--; - while (0 < plen && isspace(patch[plen])) - plen--; - fixed = 1; - } - - /* - * Check leading whitespaces (indent) - */ - for (i = 1; i < plen; i++) { - char ch = patch[i]; - if (ch == '\t') { - last_tab_in_indent = i; - if ((ws_rule & WS_SPACE_BEFORE_TAB) && - 0 < last_space_in_indent) - need_fix_leading_space = 1; - } else if (ch == ' ') { - last_space_in_indent = i; - if ((ws_rule & WS_INDENT_WITH_NON_TAB) && - 8 <= i - last_tab_in_indent) - need_fix_leading_space = 1; - } - else - break; - } - - buf = output; - if (need_fix_leading_space) { - int consecutive_spaces = 0; - int last = last_tab_in_indent + 1; - - if (ws_rule & WS_INDENT_WITH_NON_TAB) { - /* have "last" point at one past the indent */ - if (last_tab_in_indent < last_space_in_indent) - last = last_space_in_indent + 1; - else - last = last_tab_in_indent + 1; - } + int i, nr; + size_t remove_count, insert_count, applied_at = 0; + char *result; + for (i = 0; i < applied_pos; i++) + applied_at += img->line[i].len; + + remove_count = 0; + for (i = 0; i < preimage->nr; i++) + remove_count += img->line[applied_pos + i].len; + insert_count = postimage->len; + + /* Adjust the contents */ + result = xmalloc(img->len + insert_count - remove_count + 1); + memcpy(result, img->buf, applied_at); + memcpy(result + applied_at, postimage->buf, postimage->len); + memcpy(result + applied_at + postimage->len, + img->buf + (applied_at + remove_count), + img->len - (applied_at + remove_count)); + free(img->buf); + img->buf = result; + img->len += insert_count - remove_count; + result[img->len] = '\0'; + + /* Adjust the line table */ + nr = img->nr + postimage->nr - preimage->nr; + if (preimage->nr < postimage->nr) { /* - * between patch[1..last], strip the funny spaces, - * updating them to tab as needed. + * NOTE: this knows that we never call remove_first_line() + * on anything other than pre/post image. */ - for (i = 1; i < last; i++, plen--) { - char ch = patch[i]; - if (ch != ' ') { - consecutive_spaces = 0; - *output++ = ch; - } else { - consecutive_spaces++; - if (consecutive_spaces == 8) { - *output++ = '\t'; - consecutive_spaces = 0; - } - } - } - while (0 < consecutive_spaces--) - *output++ = ' '; - fixed = 1; - i = last; + img->line = xrealloc(img->line, nr * sizeof(*img->line)); + img->line_allocated = img->line; } - else - i = 1; - - memcpy(output, patch + i, plen); - if (add_nl_to_tail) - output[plen++] = '\n'; - if (fixed) - applied_after_fixing_ws++; - return output + plen - buf; + if (preimage->nr != postimage->nr) + memmove(img->line + applied_pos + postimage->nr, + img->line + applied_pos + preimage->nr, + (img->nr - (applied_pos + preimage->nr)) * + sizeof(*img->line)); + memcpy(img->line + applied_pos, + postimage->line, + postimage->nr * sizeof(*img->line)); + img->nr = nr; } -static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, +static int apply_one_fragment(struct image *img, struct fragment *frag, int inaccurate_eof, unsigned ws_rule) { int match_beginning, match_end; const char *patch = frag->patch; - int offset, size = frag->size; - char *old = xmalloc(size); - char *new = xmalloc(size); - const char *oldlines, *newlines; - int oldsize = 0, newsize = 0; + int size = frag->size; + char *old, *new, *oldlines, *newlines; int new_blank_lines_at_end = 0; unsigned long leading, trailing; - int pos, lines; + int pos, applied_pos; + struct image preimage; + struct image postimage; + + memset(&preimage, 0, sizeof(preimage)); + memset(&postimage, 0, sizeof(postimage)); + oldlines = xmalloc(size); + newlines = xmalloc(size); + old = oldlines; + new = newlines; while (size > 0) { char first; int len = linelen(patch, size); - int plen; + int plen, added; int added_blank_line = 0; if (!len) @@ -1670,7 +1859,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, * followed by "\ No newline", then we also remove the * last one (which is the newline, of course). */ - plen = len-1; + plen = len - 1; if (len < size && patch[len] == '\\') plen--; first = *patch; @@ -1687,25 +1876,40 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, if (plen < 0) /* ... followed by '\No newline'; nothing */ break; - old[oldsize++] = '\n'; - new[newsize++] = '\n'; + *old++ = '\n'; + *new++ = '\n'; + add_line_info(&preimage, "\n", 1, LINE_COMMON); + add_line_info(&postimage, "\n", 1, LINE_COMMON); break; case ' ': case '-': - memcpy(old + oldsize, patch + 1, plen); - oldsize += plen; + memcpy(old, patch + 1, plen); + add_line_info(&preimage, old, plen, + (first == ' ' ? LINE_COMMON : 0)); + old += plen; if (first == '-') break; /* Fall-through for ' ' */ case '+': - if (first != '+' || !no_add) { - int added = apply_line(new + newsize, patch, - plen, ws_rule); - newsize += added; - if (first == '+' && - added == 1 && new[newsize-1] == '\n') - added_blank_line = 1; + /* --no-add does not add new lines */ + if (first == '+' && no_add) + break; + + if (first != '+' || + !whitespace_error || + ws_error_action != correct_ws_error) { + memcpy(new, patch + 1, plen); + added = plen; } + else { + added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws); + } + add_line_info(&postimage, new, added, + (first == '+' ? 0 : LINE_COMMON)); + new += added; + if (first == '+' && + added == 1 && new[-1] == '\n') + added_blank_line = 1; break; case '@': case '\\': /* Ignore it, we already handled it */ @@ -1722,16 +1926,13 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, patch += len; size -= len; } - if (inaccurate_eof && - oldsize > 0 && old[oldsize - 1] == '\n' && - newsize > 0 && new[newsize - 1] == '\n') { - oldsize--; - newsize--; + old > oldlines && old[-1] == '\n' && + new > newlines && new[-1] == '\n') { + old--; + new--; } - oldlines = old; - newlines = new; leading = frag->leading; trailing = frag->trailing; @@ -1752,33 +1953,21 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, match_end = !trailing; } - lines = 0; - pos = frag->newpos; + pos = frag->newpos ? (frag->newpos - 1) : 0; + preimage.buf = oldlines; + preimage.len = old - oldlines; + postimage.buf = newlines; + postimage.len = new - newlines; + preimage.line = preimage.line_allocated; + postimage.line = postimage.line_allocated; + for (;;) { - offset = find_offset(buf->buf, buf->len, - oldlines, oldsize, pos, &lines); - if (match_end && offset + oldsize != buf->len) - offset = -1; - if (match_beginning && offset) - offset = -1; - if (offset >= 0) { - if (ws_error_action == correct_ws_error && - (buf->len - oldsize - offset == 0)) /* end of file? */ - newsize -= new_blank_lines_at_end; - - /* Warn if it was necessary to reduce the number - * of context lines. - */ - if ((leading != frag->leading) || - (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld)" - " to apply fragment at %d\n", - leading, trailing, pos + lines); - - strbuf_splice(buf, offset, oldsize, newlines, newsize); - offset = 0; + + applied_pos = find_pos(img, &preimage, &postimage, pos, + ws_rule, match_beginning, match_end); + + if (applied_pos >= 0) break; - } /* Am I at my context limits? */ if ((leading <= p_context) && (trailing <= p_context)) @@ -1787,33 +1976,64 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, match_beginning = match_end = 0; continue; } + /* * Reduce the number of context lines; reduce both * leading and trailing if they are equal otherwise * just reduce the larger context. */ if (leading >= trailing) { - remove_first_line(&oldlines, &oldsize); - remove_first_line(&newlines, &newsize); + remove_first_line(&preimage); + remove_first_line(&postimage); pos--; leading--; } if (trailing > leading) { - remove_last_line(&oldlines, &oldsize); - remove_last_line(&newlines, &newsize); + remove_last_line(&preimage); + remove_last_line(&postimage); trailing--; } } - if (offset && apply_verbosely) - error("while searching for:\n%.*s", oldsize, oldlines); + if (applied_pos >= 0) { + if (ws_error_action == correct_ws_error && + new_blank_lines_at_end && + postimage.nr + applied_pos == img->nr) { + /* + * If the patch application adds blank lines + * at the end, and if the patch applies at the + * end of the image, remove those added blank + * lines. + */ + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } - free(old); - free(new); - return offset; + /* + * Warn if it was necessary to reduce the number + * of context lines. + */ + if ((leading != frag->leading) || + (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld)" + " to apply fragment at %d\n", + leading, trailing, applied_pos+1); + update_image(img, applied_pos, &preimage, &postimage); + } else { + if (apply_verbosely) + error("while searching for:\n%.*s", + (int)(old - oldlines), oldlines); + } + + free(oldlines); + free(newlines); + free(preimage.line_allocated); + free(postimage.line_allocated); + + return (applied_pos < 0); } -static int apply_binary_fragment(struct strbuf *buf, struct patch *patch) +static int apply_binary_fragment(struct image *img, struct patch *patch) { struct fragment *fragment = patch->fragments; unsigned long len; @@ -1830,22 +2050,26 @@ static int apply_binary_fragment(struct strbuf *buf, struct patch *patch) } switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: - dst = patch_delta(buf->buf, buf->len, fragment->patch, + dst = patch_delta(img->buf, img->len, fragment->patch, fragment->size, &len); if (!dst) return -1; - /* XXX patch_delta NUL-terminates */ - strbuf_attach(buf, dst, len, len + 1); + clear_image(img); + img->buf = dst; + img->len = len; return 0; case BINARY_LITERAL_DEFLATED: - strbuf_reset(buf); - strbuf_add(buf, fragment->patch, fragment->size); + clear_image(img); + img->len = fragment->size; + img->buf = xmalloc(img->len+1); + memcpy(img->buf, fragment->patch, img->len); + img->buf[img->len] = '\0'; return 0; } return -1; } -static int apply_binary(struct strbuf *buf, struct patch *patch) +static int apply_binary(struct image *img, struct patch *patch) { const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned char sha1[20]; @@ -1866,7 +2090,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) * See if the old one matches what the patch * applies to. */ - hash_sha1_file(buf->buf, buf->len, blob_type, sha1); + hash_sha1_file(img->buf, img->len, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) return error("the patch applies to '%s' (%s), " "which does not match the " @@ -1875,14 +2099,14 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) } else { /* Otherwise, the old one must be empty. */ - if (buf->len) + if (img->len) return error("the patch applies to an empty " "'%s' but it is not empty", name); } get_sha1_hex(patch->new_sha1_prefix, sha1); if (is_null_sha1(sha1)) { - strbuf_release(buf); + clear_image(img); return 0; /* deletion patch */ } @@ -1897,20 +2121,21 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) return error("the necessary postimage %s for " "'%s' cannot be read", patch->new_sha1_prefix, name); - /* XXX read_sha1_file NUL-terminates */ - strbuf_attach(buf, result, size, size + 1); + clear_image(img); + img->buf = result; + img->len = size; } else { /* * We have verified buf matches the preimage; * apply the patch data to it, which is stored * in the patch->fragments->{patch,size}. */ - if (apply_binary_fragment(buf, patch)) + if (apply_binary_fragment(img, patch)) return error("binary patch does not apply to '%s'", name); /* verify that the result matches */ - hash_sha1_file(buf->buf, buf->len, blob_type, sha1); + hash_sha1_file(img->buf, img->len, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); @@ -1919,7 +2144,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) return 0; } -static int apply_fragments(struct strbuf *buf, struct patch *patch) +static int apply_fragments(struct image *img, struct patch *patch) { struct fragment *frag = patch->fragments; const char *name = patch->old_name ? patch->old_name : patch->new_name; @@ -1927,10 +2152,10 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch) unsigned inaccurate_eof = patch->inaccurate_eof; if (patch->is_binary) - return apply_binary(buf, patch); + return apply_binary(img, patch); while (frag) { - if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) { + if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -1966,6 +2191,9 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) { struct strbuf buf; + struct image image; + size_t len; + char *img; strbuf_init(&buf, 0); if (cached) { @@ -1988,9 +2216,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * } } - if (apply_fragments(&buf, patch) < 0) + img = strbuf_detach(&buf, &len); + prepare_image(&image, img, len, !patch->is_binary); + + if (apply_fragments(&image, patch) < 0) return -1; /* note with --reject this succeeds. */ - patch->result = strbuf_detach(&buf, &patch->resultsize); + patch->result = image.buf; + patch->resultsize = image.len; + free(image.line_allocated); if (0 < patch->is_delete && patch->resultsize) return error("removal patch leaves file contents"); diff --git a/builtin-fast-export.c b/builtin-fast-export.c index f741df5..4bd1356 100755 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -123,7 +123,7 @@ static void show_filemodify(struct diff_queue_struct *q, printf("D %s\n", spec->path); else { struct object *object = lookup_object(spec->sha1); - printf("M 0%06o :%d %s\n", spec->mode, + printf("M %06o :%d %s\n", spec->mode, get_object_mark(object), spec->path); } } diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index f36a43c..07d9c57 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -165,7 +165,7 @@ static int verify_format(const char *format) for (cp = format; *cp && (sp = find_next(cp)); ) { const char *ep = strchr(sp, ')'); if (!ep) - return error("malformatted format string %s", sp); + return error("malformed format string %s", sp); /* sp points at "%(" and ep points at the closing ")" */ parse_atom(sp + 2, ep); cp = ep + 1; diff --git a/builtin-init-db.c b/builtin-init-db.c index 5d7cdda..79eaf8d 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -29,27 +29,6 @@ static void safe_create_dir(const char *dir, int share) die("Could not make %s writable by group\n", dir); } -static int copy_file(const char *dst, const char *src, int mode) -{ - int fdi, fdo, status; - - mode = (mode & 0111) ? 0777 : 0666; - if ((fdi = open(src, O_RDONLY)) < 0) - return fdi; - if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) { - close(fdi); - return fdo; - } - status = copy_fd(fdi, fdo); - if (close(fdo) != 0) - return error("%s: write error: %s", dst, strerror(errno)); - - if (!status && adjust_shared_perm(dst)) - return -1; - - return status; -} - static void copy_templates_1(char *path, int baselen, char *template, int template_baselen, DIR *dir) diff --git a/builtin-rerere.c b/builtin-rerere.c index b0c17bd..c607aad 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -267,23 +267,6 @@ static int diff_two(const char *file1, const char *label1, return 0; } -static int copy_file(const char *src, const char *dest) -{ - FILE *in, *out; - char buffer[32768]; - int count; - - if (!(in = fopen(src, "r"))) - return error("Could not open %s", src); - if (!(out = fopen(dest, "w"))) - return error("Could not open %s", dest); - while ((count = fread(buffer, 1, sizeof(buffer), in))) - fwrite(buffer, 1, count, out); - fclose(in); - fclose(out); - return 0; -} - static int do_plain_rerere(struct path_list *rr, int fd) { struct path_list conflict = { NULL, 0, 0, 1 }; @@ -343,7 +326,7 @@ static int do_plain_rerere(struct path_list *rr, int fd) continue; fprintf(stderr, "Recorded resolution for '%s'.\n", path); - copy_file(path, rr_path(name, "postimage")); + copy_file(rr_path(name, "postimage"), path, 0666); tail_optimization: if (i < rr->nr - 1) memmove(rr->items + i, diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index b9af1a5..90dbb9d 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -315,7 +315,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) s = strchr(sb.buf, ' '); if (!s || *sb.buf == ' ') { o->type = OPTION_GROUP; - o->help = xstrdup(skipspaces(s)); + o->help = xstrdup(skipspaces(sb.buf)); continue; } @@ -698,6 +698,7 @@ extern const char *git_log_output_encoding; /* IO helper functions */ extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); +extern int copy_file(const char *dst, const char *src, int mode); extern int read_in_full(int fd, void *buf, size_t count); extern int write_in_full(int fd, const void *buf, size_t count); extern void write_or_die(int fd, const void *buf, size_t count); @@ -751,6 +752,7 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i #define WS_TRAILING_SPACE 01 #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 +#define WS_CR_AT_EOL 010 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); @@ -759,6 +761,7 @@ extern unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); extern char *whitespace_error_string(unsigned ws); +extern int ws_fix_copy(char *, const char *, int, unsigned, int *); /* ls-files */ int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); @@ -34,3 +34,24 @@ int copy_fd(int ifd, int ofd) close(ifd); return 0; } + +int copy_file(const char *dst, const char *src, int mode) +{ + int fdi, fdo, status; + + mode = (mode & 0111) ? 0777 : 0666; + if ((fdi = open(src, O_RDONLY)) < 0) + return fdi; + if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) { + close(fdi); + return fdo; + } + status = copy_fd(fdi, fdo); + if (close(fdo) != 0) + return error("%s: write error: %s", dst, strerror(errno)); + + if (!status && adjust_shared_perm(dst)) + return -1; + + return status; +} @@ -213,9 +213,9 @@ static const struct { { "EAST", +10, 0, }, /* Eastern Australian Standard */ { "EADT", +10, 1, }, /* Eastern Australian Daylight */ { "GST", +10, 0, }, /* Guam Standard, USSR Zone 9 */ - { "NZT", +11, 0, }, /* New Zealand */ - { "NZST", +11, 0, }, /* New Zealand Standard */ - { "NZDT", +11, 1, }, /* New Zealand Daylight */ + { "NZT", +12, 0, }, /* New Zealand */ + { "NZST", +12, 0, }, /* New Zealand Standard */ + { "NZDT", +12, 1, }, /* New Zealand Daylight */ { "IDLE", +12, 0, }, /* International Date Line East */ }; @@ -272,8 +272,8 @@ static void print_line_count(int count) } } -static void copy_file(int prefix, const char *data, int size, - const char *set, const char *reset) +static void copy_file_with_prefix(int prefix, const char *data, int size, + const char *set, const char *reset) { int ch, nl_just_seen = 1; while (0 < size--) { @@ -331,9 +331,9 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(lc_b); printf(" @@%s\n", reset); if (lc_a) - copy_file('-', one->data, one->size, old, reset); + copy_file_with_prefix('-', one->data, one->size, old, reset); if (lc_b) - copy_file('+', two->data, two->size, new, reset); + copy_file_with_prefix('+', two->data, two->size, new, reset); } static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) @@ -982,6 +982,90 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) } } +struct diffstat_dir { + struct diffstat_file **files; + int nr, percent, cumulative; +}; + +static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) +{ + unsigned long this_dir = 0; + unsigned int sources = 0; + + while (dir->nr) { + struct diffstat_file *f = *dir->files; + int namelen = strlen(f->name); + unsigned long this; + char *slash; + + if (namelen < baselen) + break; + if (memcmp(f->name, base, baselen)) + break; + slash = strchr(f->name + baselen, '/'); + if (slash) { + int newbaselen = slash + 1 - f->name; + this = gather_dirstat(dir, changed, f->name, newbaselen); + sources++; + } else { + if (f->is_unmerged || f->is_binary) + this = 0; + else + this = f->added + f->deleted; + dir->files++; + dir->nr--; + sources += 2; + } + this_dir += this; + } + + /* + * We don't report dirstat's for + * - the top level + * - or cases where everything came from a single directory + * under this directory (sources == 1). + */ + if (baselen && sources != 1) { + int permille = this_dir * 1000 / changed; + if (permille) { + int percent = permille / 10; + if (percent >= dir->percent) { + printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + if (!dir->cumulative) + return 0; + } + } + } + return this_dir; +} + +static void show_dirstat(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct diffstat_dir dir; + + /* Calculate total changes */ + changed = 0; + for (i = 0; i < data->nr; i++) { + if (data->files[i]->is_binary || data->files[i]->is_unmerged) + continue; + changed += data->files[i]->added; + changed += data->files[i]->deleted; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + dir.files = data->files; + dir.nr = data->nr; + dir.percent = options->dirstat_percent; + dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; + gather_dirstat(&dir, changed, "", 0); +} + static void free_diffstat_info(struct diffstat_t *diffstat) { int i; @@ -2050,6 +2134,7 @@ void diff_setup(struct diff_options *options) options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; + options->dirstat_percent = 3; options->context = 3; options->msg_sep = ""; @@ -2091,6 +2176,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -2102,6 +2188,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) DIFF_OPT_SET(options, RECURSIVE); @@ -2212,6 +2299,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; + else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) + options->output_format |= DIFF_FORMAT_DIRSTAT; + else if (!strcmp(arg, "--cumulative")) + options->output_format |= DIFF_FORMAT_CUMULATIVE; else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) @@ -2930,7 +3021,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2940,6 +3031,8 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat(&diffstat, options); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) @@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_SUMMARY 0x0008 #define DIFF_FORMAT_PATCH 0x0010 #define DIFF_FORMAT_SHORTSTAT 0x0020 +#define DIFF_FORMAT_DIRSTAT 0x0040 +#define DIFF_FORMAT_CUMULATIVE 0x0080 /* These override all above */ #define DIFF_FORMAT_NAME 0x0100 @@ -80,6 +82,7 @@ struct diff_options { int pickaxe_opts; int rename_score; int rename_limit; + int dirstat_percent; int setup; int abbrev; const char *msg_sep; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e8226b1..fc95e2c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -848,32 +848,73 @@ sub project_in_list { ## ---------------------------------------------------------------------- ## HTML aware string manipulation +# Try to chop given string on a word boundary between position +# $len and $len+$add_len. If there is no word boundary there, +# chop at $len+$add_len. Do not chop if chopped part plus ellipsis +# (marking chopped part) would be longer than given string. sub chop_str { my $str = shift; my $len = shift; my $add_len = shift || 10; + my $where = shift || 'right'; # 'left' | 'center' | 'right' # allow only $len chars, but don't cut a word if it would fit in $add_len # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - $body =~ s/&[^;]*$//; # remove chopped character entities + # remove chopped character entities entirely + + # when chopping in the middle, distribute $len into left and right part + # return early if chopping wouldn't make string shorter + if ($where eq 'center') { + return $str if ($len + 5 >= length($str)); # filler is length 5 + $len = int($len/2); + } else { + return $str if ($len + 4 >= length($str)); # filler is length 4 + } + + # regexps: ending and beginning with word part up to $add_len + my $endre = qr/.{$len}\w{0,$add_len}/; + my $begre = qr/\w{0,$add_len}.{$len}/; + + if ($where eq 'left') { + $str =~ m/^(.*?)($begre)$/; + my ($lead, $body) = ($1, $2); + if (length($lead) > 4) { + $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/); + $lead = " ..."; + } + return "$lead$body"; + + } elsif ($where eq 'center') { + $str =~ m/^($endre)(.*)$/; + my ($left, $str) = ($1, $2); + $str =~ m/^(.*?)($begre)$/; + my ($mid, $right) = ($1, $2); + if (length($mid) > 5) { + $left =~ s/&[^;]*$//; + $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/); + $mid = " ... "; + } + return "$left$mid$right"; + + } else { + $str =~ m/^($endre)(.*)$/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $body =~ s/&[^;]*$//; + $tail = "... "; + } + return "$body$tail"; } - return "$body$tail"; } # takes the same arguments as chop_str, but also wraps a <span> around the # result with a title attribute if it does get chopped. Additionally, the # string is HTML-escaped. sub chop_and_escape_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; + my ($str) = @_; - my $chopped = chop_str($str, $len, $add_len); + my $chopped = chop_str(@_); if ($chopped eq $str) { return esc_html($chopped); } else { @@ -3791,11 +3832,11 @@ sub git_search_grep_body { foreach my $line (@$comment) { if ($line =~ m/^(.*)($search_regexp)(.*)$/i) { my ($lead, $match, $trail) = ($1, $2, $3); - $match = chop_str($match, 70, 5); # in case match is very long - my $contextlen = int((80 - length($match))/2); # for the remainder - $contextlen = 30 if ($contextlen > 30); # but not too much - $lead = chop_str($lead, $contextlen, 10); - $trail = chop_str($trail, $contextlen, 10); + $match = chop_str($match, 70, 5, 'center'); + my $contextlen = int((80 - length($match))/2); + $contextlen = 30 if ($contextlen > 30); + $lead = chop_str($lead, $contextlen, 10, 'left'); + $trail = chop_str($trail, $contextlen, 10, 'right'); $lead = esc_html($lead); $match = esc_html($match); @@ -942,6 +942,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch int left = 1; int all_match = 0; int regflags = 0; + int fixed = 0; /* First, search for "--" */ seen_dashdash = 0; @@ -1238,6 +1239,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch regflags |= REG_ICASE; continue; } + if (!strcmp(arg, "--fixed-strings") || + !strcmp(arg, "-F")) { + fixed = 1; + continue; + } if (!strcmp(arg, "--all-match")) { all_match = 1; continue; @@ -1293,8 +1299,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } } - if (revs->grep_filter) + if (revs->grep_filter) { revs->grep_filter->regflags |= regflags; + revs->grep_filter->fixed = fixed; + } if (show_merge) prepare_show_merge(revs); diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh new file mode 100755 index 0000000..cd088b3 --- /dev/null +++ b/t/t0050-filesystem.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='Various filesystem issues' + +. ./test-lib.sh + +auml=`perl -CO -e 'print pack("U",0x00E4)'` +aumlcdiar=`perl -CO -e 'print pack("U",0x0061).pack("U",0x0308)'` + +test_expect_success 'see if we expect ' ' + + test_case=test_expect_success + test_unicode=test_expect_success + mkdir junk && + echo good >junk/CamelCase && + echo bad >junk/camelcase && + if test "$(cat junk/CamelCase)" != good + then + test_case=test_expect_failure + say "will test on a case insensitive filesystem" + fi && + rm -fr junk && + mkdir junk && + >junk/"$auml" && + case "$(cd junk && echo *)" in + "$aumlcdiar") + test_unicode=test_expect_failure + say "will test on a unicode corrupting filesystem" + ;; + *) ;; + esac && + rm -fr junk +' + +test_expect_success "setup case tests" ' + + touch camelcase && + git add camelcase && + git commit -m "initial" && + git tag initial && + git checkout -b topic && + git mv camelcase tmp && + git mv tmp CamelCase && + git commit -m "rename" && + git checkout -f master + +' + +$test_case 'rename (case change)' ' + + git mv camelcase CamelCase && + git commit -m "rename" + +' + +$test_case 'merge (case change)' ' + + git reset --hard initial && + git merge topic + +' + +test_expect_success "setup unicode normalization tests" ' + + test_create_repo unicode && + cd unicode && + touch "$aumlcdiar" && + git add "$aumlcdiar" && + git commit -m initial + git tag initial && + git checkout -b topic && + git mv $aumlcdiar tmp && + git mv tmp "$auml" && + git commit -m rename && + git checkout -f master + +' + +$test_unicode 'rename (silent unicode normalization)' ' + + git mv "$aumlcdiar" "$auml" && + git commit -m rename + +' + +$test_unicode 'merge (silent unicode normalization)' ' + + git reset --hard initial && + git merge topic + +' + +test_done diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh new file mode 100755 index 0000000..762af5f --- /dev/null +++ b/t/t1502-rev-parse-parseopt.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='test git rev-parse --parseopt' +. ./test-lib.sh + +cat > expect.err <<EOF +usage: some-command [options] <args>... + + some-command does foo and bar! + + -h, --help show the help + --foo some nifty option --foo + --bar ... some cool option --bar with an argument + +An option group Header + -C [...] option C with an optional argument + +Extras + --extra1 line above used to cause a segfault but no longer does + +EOF + +test_expect_success 'test --parseopt help output' ' + git rev-parse --parseopt -- -h 2> output.err <<EOF +some-command [options] <args>... + +some-command does foo and bar! +-- +h,help show the help + +foo some nifty option --foo +bar= some cool option --bar with an argument + + An option group Header +C? option C with an optional argument + +Extras +extra1 line above used to cause a segfault but no longer does +EOF + git diff expect.err output.err +' + +test_done diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 67e080b..0d9cbb6 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -12,6 +12,7 @@ test_expect_success setup ' echo " Eight SP indent" >>F && echo " HT and SP indent" >>F && echo "With trailing SP " >>F && + echo "Carriage ReturnQ" | tr Q "\015" >>F && echo "No problem" >>F ' @@ -27,6 +28,7 @@ test_expect_success default ' grep Eight normal >/dev/null && grep HT error >/dev/null && grep With error >/dev/null && + grep Return error >/dev/null && grep No normal >/dev/null ' @@ -41,6 +43,7 @@ test_expect_success 'without -trail' ' grep Eight normal >/dev/null && grep HT error >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' @@ -56,6 +59,7 @@ test_expect_success 'without -trail (attribute)' ' grep Eight normal >/dev/null && grep HT error >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' @@ -71,6 +75,7 @@ test_expect_success 'without -space' ' grep Eight normal >/dev/null && grep HT normal >/dev/null && grep With error >/dev/null && + grep Return error >/dev/null && grep No normal >/dev/null ' @@ -86,6 +91,7 @@ test_expect_success 'without -space (attribute)' ' grep Eight normal >/dev/null && grep HT normal >/dev/null && grep With error >/dev/null && + grep Return error >/dev/null && grep No normal >/dev/null ' @@ -101,6 +107,7 @@ test_expect_success 'with indent-non-tab only' ' grep Eight error >/dev/null && grep HT normal >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' @@ -116,6 +123,39 @@ test_expect_success 'with indent-non-tab only (attribute)' ' grep Eight error >/dev/null && grep HT normal >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'with cr-at-eol' ' + + rm -f .gitattributes + git config core.whitespace cr-at-eol + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With error >/dev/null && + grep Return normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'with cr-at-eol (attribute)' ' + + git config --unset core.whitespace + echo "F whitespace=trailing,cr-at-eol" >.gitattributes + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With error >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' diff --git a/t/t4105-apply-fuzz.sh b/t/t4105-apply-fuzz.sh new file mode 100755 index 0000000..0e8d25f --- /dev/null +++ b/t/t4105-apply-fuzz.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +test_description='apply with fuzz and offset' + +. ./test-lib.sh + +dotest () { + name="$1" && shift && + test_expect_success "$name" " + git checkout-index -f -q -u file && + git apply $* && + diff -u expect file + " +} + +test_expect_success setup ' + + for i in 1 2 3 4 5 6 7 8 9 10 11 12 + do + echo $i + done >file && + git update-index --add file && + for i in 1 2 3 4 5 6 7 a b c d e 8 9 10 11 12 + do + echo $i + done >file && + cat file >expect && + git diff >O0.diff && + + sed -e "s/@@ -5,6 +5,11 @@/@@ -2,6 +2,11 @@/" >O1.diff O0.diff && + sed -e "s/@@ -5,6 +5,11 @@/@@ -7,6 +7,11 @@/" >O2.diff O0.diff && + sed -e "s/@@ -5,6 +5,11 @@/@@ -19,6 +19,11 @@/" >O3.diff O0.diff && + + sed -e "s/^ 5/ S/" >F0.diff O0.diff && + sed -e "s/^ 5/ S/" >F1.diff O1.diff && + sed -e "s/^ 5/ S/" >F2.diff O2.diff && + sed -e "s/^ 5/ S/" >F3.diff O3.diff + +' + +dotest 'unmodified patch' O0.diff + +dotest 'minus offset' O1.diff + +dotest 'plus offset' O2.diff + +dotest 'big offset' O3.diff + +dotest 'fuzz with no offset' -C2 F0.diff + +dotest 'fuzz with minus offset' -C2 F1.diff + +dotest 'fuzz with plus offset' -C2 F2.diff + +dotest 'fuzz with big offset' -C2 F3.diff + +test_done diff --git a/t/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh new file mode 100755 index 0000000..d6f15be --- /dev/null +++ b/t/t4125-apply-ws-fuzz.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +test_description='applying patch that has broken whitespaces in context' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + + # file-0 is full of whitespace breakages + for l in a bb c d eeee f ggg h + do + echo "$l " + done >file-0 && + + # patch-0 creates a whitespace broken file + cat file-0 >file && + git diff >patch-0 && + git add file && + + # file-1 is still full of whitespace breakages, + # but has one line updated, without fixing any + # whitespaces. + # patch-1 records that change. + sed -e "s/d/D/" file-0 >file-1 && + cat file-1 >file && + git diff >patch-1 && + + # patch-all is the effect of both patch-0 and patch-1 + >file && + git add file && + cat file-1 >file && + git diff >patch-all && + + # patch-2 is the same as patch-1 but is based + # on a version that already has whitespace fixed, + # and does not introduce whitespace breakages. + sed -e "s/ $//" patch-1 >patch-2 && + + # If all whitespace breakages are fixed the contents + # should look like file-fixed + sed -e "s/ $//" file-1 >file-fixed + +' + +test_expect_success nofix ' + + >file && + git add file && + + # Baseline. Applying without fixing any whitespace + # breakages. + git apply --whitespace=nowarn patch-0 && + git apply --whitespace=nowarn patch-1 && + + # The result should obviously match. + diff -u file-1 file +' + +test_expect_success 'withfix (forward)' ' + + >file && + git add file && + + # The first application will munge the context lines + # the second patch depends on. We should be able to + # adjust and still apply. + git apply --whitespace=fix patch-0 && + git apply --whitespace=fix patch-1 && + + diff -u file-fixed file +' + +test_expect_success 'withfix (backward)' ' + + >file && + git add file && + + # Now we have a whitespace breakages on our side. + git apply --whitespace=nowarn patch-0 && + + # And somebody sends in a patch based on image + # with whitespace already fixed. + git apply --whitespace=fix patch-2 && + + # The result should accept the whitespace fixed + # postimage. But the line with "h" is beyond context + # horizon and left unfixed. + + sed -e /h/d file-fixed >fixed-head && + sed -e /h/d file >file-head && + diff -u fixed-head file-head && + + sed -n -e /h/p file-fixed >fixed-tail && + sed -n -e /h/p file >file-tail && + + ! diff -u fixed-tail file-tail + +' + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 2efaed4..cbbfa9c 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -15,16 +15,22 @@ test_expect_success \ 'Setup helper tool' \ '(echo "#!/bin/sh" echo shift + echo output=1 + echo "while test -f commandline\$output; do output=\$((\$output+1)); done" echo for a echo do echo " echo \"!\$a!\"" - echo "done >commandline" - echo "cat > msgtxt" + echo "done >commandline\$output" + echo "cat > msgtxt\$output" ) >fake.sendmail && chmod +x ./fake.sendmail && git add fake.sendmail && GIT_AUTHOR_NAME="A" git commit -a -m "Second."' +clean_fake_sendmail() { + rm -f commandline* msgtxt* +} + test_expect_success 'Extract patches' ' patches=`git format-patch -n HEAD^1` ' @@ -39,7 +45,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'Verify commandline' \ - 'diff commandline expected' + 'diff commandline1 expected' cat >expected-show-all-headers <<\EOF 0001-Second.patch @@ -82,7 +88,7 @@ z8=zzzzzzzz z64=$z8$z8$z8$z8$z8$z8$z8$z8 z512=$z64$z64$z64$z64$z64$z64$z64$z64 test_expect_success 'reject long lines' ' - rm -f commandline && + clean_fake_sendmail && cp $patches longline.patch && echo $z512$z512 >>longline.patch && ! git send-email \ @@ -95,7 +101,7 @@ test_expect_success 'reject long lines' ' ' test_expect_success 'no patch was sent' ' - ! test -e commandline + ! test -e commandline1 ' test_expect_success 'allow long lines with --no-validate' ' @@ -109,6 +115,7 @@ test_expect_success 'allow long lines with --no-validate' ' ' test_expect_success 'Invalid In-Reply-To' ' + clean_fake_sendmail && git send-email \ --from="Example <nobody@example.com>" \ --to=nobody@example.com \ @@ -116,17 +123,47 @@ test_expect_success 'Invalid In-Reply-To' ' --smtp-server="$(pwd)/fake.sendmail" \ $patches 2>errors - ! grep "^In-Reply-To: < *>" msgtxt + ! grep "^In-Reply-To: < *>" msgtxt1 ' test_expect_success 'Valid In-Reply-To when prompting' ' + clean_fake_sendmail && (echo "From Example <from@example.com>" echo "To Example <to@example.com>" echo "" ) | env GIT_SEND_EMAIL_NOTTY=1 git send-email \ --smtp-server="$(pwd)/fake.sendmail" \ $patches 2>errors && - ! grep "^In-Reply-To: < *>" msgtxt + ! grep "^In-Reply-To: < *>" msgtxt1 +' + +test_expect_success 'setup fake editor' ' + (echo "#!/bin/sh" && + echo "echo fake edit >>\$1" + ) >fake-editor && + chmod +x fake-editor +' + +test_expect_success '--compose works' ' + clean_fake_sendmail && + echo y | \ + GIT_EDITOR=$(pwd)/fake-editor \ + GIT_SEND_EMAIL_NOTTY=1 \ + git send-email \ + --compose --subject foo \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches \ + 2>errors +' + +test_expect_success 'first message is compose text' ' + grep "^fake edit" msgtxt1 +' + +test_expect_success 'second message is patch' ' + grep "Subject:.*Second" msgtxt2 ' test_done @@ -14,6 +14,7 @@ static struct whitespace_rule { { "trailing-space", WS_TRAILING_SPACE }, { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, + { "cr-at-eol", WS_CR_AT_EOL }, }; unsigned parse_whitespace_rule(const char *string) @@ -124,6 +125,7 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, int written = 0; int trailing_whitespace = -1; int trailing_newline = 0; + int trailing_carriage_return = 0; int i; /* Logic is simpler if we temporarily ignore the trailing newline. */ @@ -131,6 +133,11 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, trailing_newline = 1; len--; } + if ((ws_rule & WS_CR_AT_EOL) && + len > 0 && line[len - 1] == '\r') { + trailing_carriage_return = 1; + len--; + } /* Check for trailing whitespace. */ if (ws_rule & WS_TRAILING_SPACE) { @@ -176,8 +183,10 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, } if (stream) { - /* Now the rest of the line starts at written. - * The non-highlighted part ends at trailing_whitespace. */ + /* + * Now the rest of the line starts at "written". + * The non-highlighted part ends at "trailing_whitespace". + */ if (trailing_whitespace == -1) trailing_whitespace = len; @@ -196,8 +205,114 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, len - trailing_whitespace, 1, stream); fputs(reset, stream); } + if (trailing_carriage_return) + fputc('\r', stream); if (trailing_newline) fputc('\n', stream); } return result; } + +/* Copy the line to the buffer while fixing whitespaces */ +int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count) +{ + /* + * len is number of bytes to be copied from src, starting + * at src. Typically src[len-1] is '\n', unless this is + * the incomplete last line. + */ + int i; + int add_nl_to_tail = 0; + int add_cr_to_tail = 0; + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + /* + * Strip trailing whitespace + */ + if ((ws_rule & WS_TRAILING_SPACE) && + (2 <= len && isspace(src[len-2]))) { + if (src[len - 1] == '\n') { + add_nl_to_tail = 1; + len--; + if (1 < len && src[len - 1] == '\r') { + add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL); + len--; + } + } + if (0 < len && isspace(src[len - 1])) { + while (0 < len && isspace(src[len-1])) + len--; + fixed = 1; + } + } + + /* + * Check leading whitespaces (indent) + */ + for (i = 0; i < len; i++) { + char ch = src[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if ((ws_rule & WS_SPACE_BEFORE_TAB) && + 0 <= last_space_in_indent) + need_fix_leading_space = 1; + } else if (ch == ' ') { + last_space_in_indent = i; + if ((ws_rule & WS_INDENT_WITH_NON_TAB) && + 8 <= i - last_tab_in_indent) + need_fix_leading_space = 1; + } else + break; + } + + buf = dst; + if (need_fix_leading_space) { + /* Process indent ourselves */ + int consecutive_spaces = 0; + int last = last_tab_in_indent + 1; + + if (ws_rule & WS_INDENT_WITH_NON_TAB) { + /* have "last" point at one past the indent */ + if (last_tab_in_indent < last_space_in_indent) + last = last_space_in_indent + 1; + else + last = last_tab_in_indent + 1; + } + + /* + * between src[0..last-1], strip the funny spaces, + * updating them to tab as needed. + */ + for (i = 0; i < last; i++) { + char ch = src[i]; + if (ch != ' ') { + consecutive_spaces = 0; + *dst++ = ch; + } else { + consecutive_spaces++; + if (consecutive_spaces == 8) { + *dst++ = '\t'; + consecutive_spaces = 0; + } + } + } + while (0 < consecutive_spaces--) + *dst++ = ' '; + len -= last; + src += last; + fixed = 1; + } + + memcpy(dst, src, len); + if (add_cr_to_tail) + dst[len++] = '\r'; + if (add_nl_to_tail) + dst[len++] = '\n'; + if (fixed && error_count) + (*error_count)++; + return dst + len - buf; +} |