Skip to content

Commit e3232af

Browse files
authored
Merge pull request #14253 from NixOS/libgit2-refname-wa
libfetchers/git-utils: Be more correct about validating refnames
2 parents a543519 + 5d1178b commit e3232af

File tree

4 files changed

+29
-45
lines changed

4 files changed

+29
-45
lines changed

src/libfetchers-tests/git-utils.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ TEST_F(GitUtilsTest, peel_reference)
175175

176176
TEST(GitUtils, isLegalRefName)
177177
{
178+
ASSERT_TRUE(isLegalRefName("A/b"));
179+
ASSERT_TRUE(isLegalRefName("AaA/b"));
180+
ASSERT_TRUE(isLegalRefName("FOO/BAR/BAZ"));
181+
ASSERT_TRUE(isLegalRefName("HEAD"));
182+
ASSERT_TRUE(isLegalRefName("refs/tags/1.2.3"));
183+
ASSERT_TRUE(isLegalRefName("refs/heads/master"));
178184
ASSERT_TRUE(isLegalRefName("foox"));
179185
ASSERT_TRUE(isLegalRefName("1337"));
180186
ASSERT_TRUE(isLegalRefName("foo.baz"));

src/libfetchers/git-utils.cc

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <git2/attr.h>
1414
#include <git2/blob.h>
15+
#include <git2/branch.h>
1516
#include <git2/commit.h>
1617
#include <git2/config.h>
1718
#include <git2/describe.h>
@@ -28,6 +29,7 @@
2829
#include <git2/submodule.h>
2930
#include <git2/sys/odb_backend.h>
3031
#include <git2/sys/mempack.h>
32+
#include <git2/tag.h>
3133
#include <git2/tree.h>
3234

3335
#include <boost/unordered/unordered_flat_map.hpp>
@@ -1323,63 +1325,33 @@ GitRepo::WorkdirInfo GitRepo::getCachedWorkdirInfo(const std::filesystem::path &
13231325
return workdirInfo;
13241326
}
13251327

1326-
/**
1327-
* Checks that the git reference is valid and normalizes slash '/' sequences.
1328-
*
1329-
* Accepts shorthand references (one-level refnames are allowed).
1330-
*/
1331-
bool isValidRefNameAllowNormalizations(const std::string & refName)
1332-
{
1333-
/* Unfortunately libgit2 doesn't expose the limit in headers, but its internal
1334-
limit is also 1024. */
1335-
std::array<char, 1024> normalizedRefBuffer;
1336-
1337-
/* It would be nice to have a better API like git_reference_name_is_valid, but
1338-
* with GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND flag. libgit2 uses it internally
1339-
* but doesn't expose it in public headers [1].
1340-
* [1]:
1341-
* https://github.com/libgit2/libgit2/blob/9d5f1bacc23594c2ba324c8f0d41b88bf0e9ef04/src/libgit2/refs.c#L1362-L1365
1342-
*/
1343-
1344-
auto res = git_reference_normalize_name(
1345-
normalizedRefBuffer.data(),
1346-
normalizedRefBuffer.size(),
1347-
refName.c_str(),
1348-
GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND);
1349-
1350-
return res == 0;
1351-
}
1352-
13531328
bool isLegalRefName(const std::string & refName)
13541329
{
13551330
initLibGit2();
13561331

1357-
/* Since `git_reference_normalize_name` is the best API libgit2 has for verifying
1358-
* reference names with shorthands (see comment in normalizeRefName), we need to
1359-
* ensure that exceptions to the validity checks imposed by normalization [1] are checked
1360-
* explicitly.
1361-
* [1]: https://git-scm.com/docs/git-check-ref-format#Documentation/git-check-ref-format.txt---normalize
1362-
*/
1363-
13641332
/* Check for cases that don't get rejected by libgit2.
13651333
* FIXME: libgit2 should reject this. */
13661334
if (refName == "@")
13671335
return false;
13681336

1369-
/* Leading slashes and consecutive slashes are stripped during normalizatiton. */
1370-
if (refName.starts_with('/') || refName.find("//") != refName.npos)
1371-
return false;
1372-
1373-
/* Refer to libgit2. */
1374-
if (!isValidRefNameAllowNormalizations(refName))
1375-
return false;
1376-
13771337
/* libgit2 doesn't barf on DEL symbol.
13781338
* FIXME: libgit2 should reject this. */
13791339
if (refName.find('\177') != refName.npos)
13801340
return false;
13811341

1382-
return true;
1342+
for (auto * func : {
1343+
git_reference_name_is_valid,
1344+
git_branch_name_is_valid,
1345+
git_tag_name_is_valid,
1346+
}) {
1347+
int valid = 0;
1348+
if (func(&valid, refName.c_str()))
1349+
throw Error("checking git reference '%s': %s", refName, git_error_last()->message);
1350+
if (valid)
1351+
return true;
1352+
}
1353+
1354+
return false;
13831355
}
13841356

13851357
} // namespace nix

src/libfetchers/include/nix/fetchers/git-utils.hh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,12 @@ struct Setter
158158
};
159159

160160
/**
161-
* Checks that the git reference is valid and normalized.
161+
* Checks that the string can be a valid git reference, branch or tag name.
162+
* Accepts shorthand references (one-level refnames are allowed), pseudorefs
163+
* like `HEAD`.
162164
*
163-
* Accepts shorthand references (one-level refnames are allowed).
165+
* @note This is a coarse test to make sure that the refname is at least something
166+
* that Git can make sense of.
164167
*/
165168
bool isLegalRefName(const std::string & refName);
166169

tests/functional/fetchGitRefs.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ invalid_ref() {
5959
}
6060

6161

62+
valid_ref 'A/b'
63+
valid_ref 'AaA/b'
64+
valid_ref 'FOO/BAR/BAZ'
6265
valid_ref 'foox'
6366
valid_ref '1337'
6467
valid_ref 'foo.baz'

0 commit comments

Comments
 (0)