From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS31976 209.132.180.0/23 X-Spam-Status: No, score=-3.9 required=3.0 tests=AWL,BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI, RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by dcvr.yhbt.net (Postfix) with ESMTP id 8E8E91F4B9 for ; Fri, 24 Jan 2020 21:19:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387490AbgAXVT7 (ORCPT ); Fri, 24 Jan 2020 16:19:59 -0500 Received: from mail-wr1-f66.google.com ([209.85.221.66]:47020 "EHLO mail-wr1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729623AbgAXVTw (ORCPT ); Fri, 24 Jan 2020 16:19:52 -0500 Received: by mail-wr1-f66.google.com with SMTP id z7so3680106wrl.13 for ; Fri, 24 Jan 2020 13:19:51 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=ZEZzbz6iLF3EEQHCPFq0S7F2RB0HRzMZVzfkhYJ1Yb8=; b=K+dKAaPaOnCI5c0eXsWPLi9421vjRCRRo8GNVW/EvMia3rc8fPKE2IlF4w5YezRT8u cIWoZ+nrMYllIKD3gzjeUQHbKX1/qxCTJgSf8qD0H4WmDVCmy7Qo1RDx16nWSasNxXwf GM0rf2vhBAjyhxsPkbFPPcdc/zk79YG0ZPGdS7WFqWq6jTnhY1liM7b8YUb2yG93+smn OJtTIdLLDulzYxWlzUsQN58rvLK7WMplL9s2GkvvttOY7/vEWcvdOfOf1lWtUwqc37os x31HIOOO7TSOF6xNseN1ImC3B7uzad2Nzk6gYPsiXbadxN4XSuYBmDIa83UP2GFmwkDO Bvtw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=ZEZzbz6iLF3EEQHCPFq0S7F2RB0HRzMZVzfkhYJ1Yb8=; b=V0zZGk2amNrjb8PLkgREq0yRIpBPzoKDHBoEZx+aVo275qAv521DoCX3SbmpQ54G7T cQ5i4Ce0B/yUa+ws32PRS5tMlRhgJQY+TpCxAg2fm/P0UoyZttRSGsyPBV67erSQD/wW a5SLtQB16mj9SeG0CCU6I1c03rNZODc5yepMsDIWTTUGzoX8fv8OgLZ3lskNitHxZFSv iwin45n4ZTmnggVTwGKsgtQtOuTn8TU6tnvpIZovSiafTtKT39Nc5JYkjgG/dopzLoXG 01+1g0LXZR87aydbLjJ74uMgyNrBCEIT66iQxSOwwZWn9UBqVUyG8HxiqnU6+gMzHJcz TaVg== X-Gm-Message-State: APjAAAVdVvLl0Bcmj1xnuMXGGyZ14qirvaEQquAM2Kj2L9XwPnu9GHOD t8y8DcwvE6vF4DavlxSDQcVJqmQZ X-Google-Smtp-Source: APXvYqy4HhRCVFGLGMK7NyRjg4/Lfrod66gxfgfXkUwFM08903iuGzz4T6N9h5dCHtO7pvxATNQ00g== X-Received: by 2002:a5d:4044:: with SMTP id w4mr6331359wrp.322.1579900790886; Fri, 24 Jan 2020 13:19:50 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id i11sm9205570wrs.10.2020.01.24.13.19.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 24 Jan 2020 13:19:50 -0800 (PST) Message-Id: In-Reply-To: References: From: "Derrick Stolee via GitGitGadget" Date: Fri, 24 Jan 2020 21:19:40 +0000 Subject: [PATCH v2 10/12] sparse-checkout: write escaped patterns in cone mode Fcc: Sent Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MIME-Version: 1.0 To: git@vger.kernel.org Cc: me@ttaylorr.com, peff@peff.net, newren@gmail.com, Derrick Stolee , Derrick Stolee Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Derrick Stolee If a user somehow creates a directory with an asterisk (*) or backslash (\), then the "git sparse-checkout set" command will struggle to provide the correct pattern in the sparse-checkout file. When not in cone mode, the provided pattern is written directly into the sparse-checkout file. However, in cone mode we expect a list of paths to directories and then we convert those into patterns. Even more specifically, the goal is to always allow the following from the root of a repo: git ls-tree --name-only -d HEAD | git sparse-checkout set --stdin The ls-tree command provides directory names with an unescaped asterisk. It also quotes the directories that contain an escaped backslash. We must remove these quotes, then keep the escaped backslashes. However, there is some care needed for the timing of these escapes. The in-memory pattern list is used to update the working directory before writing the patterns to disk. Thus, we need the command to have the unescaped names in the hashsets for the cone comparisons, then escape the patterns later. Signed-off-by: Derrick Stolee --- builtin/sparse-checkout.c | 48 ++++++++++++++++++++++++++++-- t/t1091-sparse-checkout-builtin.sh | 21 +++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 3cee8ab46e..61d2c30036 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -140,6 +140,22 @@ static int update_working_directory(struct pattern_list *pl) return result; } +static char *escaped_pattern(char *pattern) +{ + char *p = pattern; + struct strbuf final = STRBUF_INIT; + + while (*p) { + if (*p == '*' || *p == '\\') + strbuf_addch(&final, '\\'); + + strbuf_addch(&final, *p); + p++; + } + + return strbuf_detach(&final, NULL); +} + static void write_cone_to_file(FILE *fp, struct pattern_list *pl) { int i; @@ -164,10 +180,11 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl) fprintf(fp, "/*\n!/*/\n"); for (i = 0; i < sl.nr; i++) { - char *pattern = sl.items[i].string; + char *pattern = escaped_pattern(sl.items[i].string); if (strlen(pattern)) fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern); + free(pattern); } string_list_clear(&sl, 0); @@ -185,8 +202,9 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl) string_list_remove_duplicates(&sl, 0); for (i = 0; i < sl.nr; i++) { - char *pattern = sl.items[i].string; + char *pattern = escaped_pattern(sl.items[i].string); fprintf(fp, "%s/\n", pattern); + free(pattern); } } @@ -337,7 +355,9 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat { struct pattern_entry *e = xmalloc(sizeof(*e)); e->patternlen = path->len; - e->pattern = strbuf_detach(path, NULL); + e->pattern = dup_and_filter_pattern(path->buf); + strbuf_release(path); + hashmap_entry_init(&e->ent, ignore_case ? strihash(e->pattern) : @@ -369,6 +389,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) { + int i; strbuf_trim(line); strbuf_trim_trailing_dir_sep(line); @@ -376,6 +397,27 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) if (!line->len) return; + for (i = 0; i < line->len; i++) { + if (line->buf[i] == '*') { + strbuf_insert(line, i, "\\", 1); + i++; + } + + if (line->buf[i] == '\\') { + if (i < line->len - 1 && line->buf[i + 1] == '\\') + i++; + else + strbuf_insert(line, i, "\\", 1); + + i++; + } + } + + if (line->buf[0] == '"' && line->buf[line->len - 1] == '"') { + strbuf_remove(line, 0, 1); + strbuf_remove(line, line->len - 1, 1); + } + if (line->buf[0] != '/') strbuf_insert(line, 0, "/", 1); diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 0a21a5e15d..2bb30cbe29 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -309,6 +309,9 @@ check_read_tree_errors () { REPO=$1 FILES=$2 ERRORS=$3 + git -C $REPO -c core.sparseCheckoutCone=false read-tree -mu HEAD 2>err && + test_must_be_empty err && + check_files $REPO "$FILES" && git -C $REPO read-tree -mu HEAD 2>err && if test -z "$ERRORS" then @@ -379,14 +382,28 @@ test_expect_success BSLASHPSPEC 'pattern-checks: escaped "*"' ' git -C escaped reset --hard $COMMIT && check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" && git -C escaped sparse-checkout init --cone && - cat >escaped/.git/info/sparse-checkout <<-\EOF && + git -C escaped sparse-checkout set zbad\\dir zdoes\*not\*exist zdoes\*exist && + cat >expect <<-\EOF && /* !/*/ /zbad\\dir/ + /zdoes\*exist/ /zdoes\*not\*exist/ + EOF + test_cmp expect escaped/.git/info/sparse-checkout && + check_read_tree_errors escaped "a zbad\\dir zdoes*exist" && + git -C escaped ls-tree -d --name-only HEAD | git -C escaped sparse-checkout set --stdin && + cat >expect <<-\EOF && + /* + !/*/ + /deep/ + /folder1/ + /folder2/ + /zbad\\dir/ /zdoes\*exist/ EOF - check_read_tree_errors escaped "a zbad\\dir zdoes*exist" + test_cmp expect escaped/.git/info/sparse-checkout && + check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" ' test_done -- gitgitgadget