Skip to content

Commit 4f51f5a

Browse files
committed
Fetch all refs unless using Git :depth option
Reverts to the behavior from Elixir/Mix 1.16 when the :depth option didn't exist. An optimization to fetch only the about-to-be-used refs introduced when the :depth option was added broke support for specifying a SHA1 prefix as :ref. Such possibility was neither documented nor tested, but certainly used in the wild. This use case is now explicitly supported with a new test case. When using :ref with :depth, a full SHA1 is required by Git and surfaced in our docs. For now, we don't enforce it when validating the options to avoid unnecessary coupling with current Git behavior -- if a new Git client version and a server supports resolving short refs, or if the repo is using a different hash algorithm altogether (e.g. SHA256), `mix` will not prevent it from being used. Elixir maintainers may become aware of such changes through a new test case that verifies the currently observed behavior. This commit also brings back support for :ref and :depth being used together (with the caveat above on :ref), along with two tests that cover that functionality.
1 parent d9cf285 commit 4f51f5a

File tree

3 files changed

+95
-21
lines changed

3 files changed

+95
-21
lines changed

lib/mix/lib/mix/scm/git.ex

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule Mix.SCM.Git do
99

1010
@impl true
1111
def format(opts) do
12-
if rev = opts[:ref] || opts[:branch] || opts[:tag] do
12+
if rev = get_opts_rev(opts) do
1313
"#{redact_uri(opts[:git])} - #{rev}"
1414
else
1515
redact_uri(opts[:git])
@@ -126,17 +126,16 @@ defmodule Mix.SCM.Git do
126126
update_origin(opts[:git])
127127

128128
# Fetch external data
129-
branch_or_tag = opts[:branch] || opts[:tag]
129+
rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
130130

131131
["--git-dir=.git", "fetch", "--force", "--quiet"]
132132
|> Kernel.++(progress_switch(git_version()))
133133
|> Kernel.++(tags_switch(opts[:tag]))
134-
|> Kernel.++(depth_switch(opts[:depth]))
135-
|> Kernel.++(if branch_or_tag, do: ["origin", branch_or_tag], else: [])
134+
|> Kernel.++(depth_switch(opts[:depth], rev))
136135
|> git!()
137136

138137
# Migrate the Git repo
139-
rev = get_lock_rev(opts[:lock], opts) || opts[:ref] || branch_or_tag || default_branch()
138+
rev = rev || default_branch()
140139
git!(["--git-dir=.git", "checkout", "--quiet", rev])
141140

142141
if opts[:submodules] do
@@ -212,11 +211,11 @@ defmodule Mix.SCM.Git do
212211
defp tags_switch(nil), do: []
213212
defp tags_switch(_), do: ["--tags"]
214213

215-
defp depth_switch(nil), do: []
214+
defp depth_switch(nil, _rev), do: []
216215

217-
defp depth_switch(n) when is_integer(n) and n > 0 do
216+
defp depth_switch(n, rev) when is_integer(n) and n > 0 do
218217
check_depth_support(git_version())
219-
["--depth=#{n}"]
218+
["--depth=#{n}"] ++ if rev, do: ["origin", rev], else: []
220219
end
221220

222221
## Helpers
@@ -250,24 +249,18 @@ defmodule Mix.SCM.Git do
250249
end
251250

252251
defp validate_depth(opts) do
253-
case Keyword.take(opts, [:depth, :ref]) do
254-
[_, _] ->
255-
Mix.raise(
256-
"Cannot specify :depth and :ref at the same time. " <>
257-
"Error on Git dependency: #{redact_uri(opts[:git])}"
258-
)
252+
case Keyword.take(opts, [:depth]) do
253+
[] ->
254+
opts
259255

260-
[depth: depth] when is_integer(depth) and depth > 0 ->
256+
[{:depth, depth}] when is_integer(depth) and depth > 0 ->
261257
opts
262258

263-
[depth: invalid_depth] ->
259+
invalid_depth ->
264260
Mix.raise(
265261
"The depth must be a positive integer, and be specified only once, got: #{inspect(invalid_depth)}. " <>
266262
"Error on Git dependency: #{redact_uri(opts[:git])}"
267263
)
268-
269-
_ ->
270-
opts
271264
end
272265
end
273266

@@ -296,6 +289,10 @@ defmodule Mix.SCM.Git do
296289
end
297290
end
298291

292+
defp get_opts_rev(opts) do
293+
opts[:branch] || opts[:ref] || opts[:tag]
294+
end
295+
299296
defp redact_uri(git) do
300297
case URI.parse(git) do
301298
%{userinfo: nil} -> git

lib/mix/lib/mix/tasks/deps.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ defmodule Mix.Tasks.Deps do
125125
* `:depth` *(since v1.17.0)* - creates a shallow clone of the Git repository,
126126
limiting the history to the specified number of commits. This can significantly
127127
improve clone speed for large repositories when full history is not needed.
128-
The value must be a positive integer, typically `1`. Cannot be used with the
129-
`:ref` option.
128+
The value must be a positive integer, typically `1`. When using `:depth` with
129+
`:ref`, a fully spelled hex object name (a 40-character SHA-1 hash) is required.
130130
131131
If your Git repository requires authentication, such as basic username:password
132132
HTTP authentication via URLs, it can be achieved via Git configuration, keeping

lib/mix/test/mix/tasks/deps.git_test.exs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,22 @@ defmodule Mix.Tasks.DepsGitTest do
478478
purge([GitRepo, GitRepo.MixProject])
479479
end
480480

481+
test "fetches with short ref" do
482+
[<<short_sha1::binary-size(8), _::binary>>, _ | _] = get_git_repo_revs("git_repo")
483+
484+
Process.put(:git_repo_opts, ref: short_sha1)
485+
486+
in_fixture("no_mixfile", fn ->
487+
Mix.Project.push(GitApp)
488+
489+
Mix.Tasks.Deps.Get.run([])
490+
message = "* Getting git_repo (#{fixture_path("git_repo")} - #{short_sha1})"
491+
assert_received {:mix_shell, :info, [^message]}
492+
493+
refute_received {:mix_shell, :error, _}
494+
end)
495+
end
496+
481497
describe "Git depth option" do
482498
@describetag :git_depth
483499

@@ -530,6 +546,67 @@ defmodule Mix.Tasks.DepsGitTest do
530546
end)
531547
end
532548

549+
test "with ref" do
550+
[sha1, _ | _] = get_git_repo_revs("git_repo")
551+
552+
Process.put(:git_repo_opts, depth: 1, ref: sha1)
553+
554+
in_fixture("no_mixfile", fn ->
555+
Mix.Project.push(GitApp)
556+
557+
Mix.Tasks.Deps.Get.run([])
558+
message = "* Getting git_repo (#{fixture_path("git_repo")} - #{sha1})"
559+
assert_received {:mix_shell, :info, [^message]}
560+
assert_shallow("deps/git_repo", 1)
561+
end)
562+
end
563+
564+
test "fails to find short ref" do
565+
# Failure to resolve a short ref depends on Git behavior.
566+
#
567+
# If Git ever changes such that it can resolve a short ref in a shallow
568+
# fetch, we can update our docs and this test to reflect that. For now,
569+
# when fetching a specific refspec, Git requires a fully spelled hex
570+
# object name as the <src>.
571+
#
572+
# https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt-ltrefspecgt
573+
# https://stackoverflow.com/a/43136160
574+
575+
[<<short_sha1::binary-size(8), _::binary>>, _ | _] = get_git_repo_revs("git_repo")
576+
577+
Process.put(:git_repo_opts, depth: 1, ref: short_sha1)
578+
579+
in_fixture("no_mixfile", fn ->
580+
Mix.Project.push(GitApp)
581+
582+
exception = assert_raise Mix.Error, fn -> Mix.Tasks.Deps.Get.run([]) end
583+
584+
assert Exception.message(exception) =~ "fatal: couldn't find remote ref"
585+
end)
586+
end
587+
588+
test "changing refspec updates retaining depth" do
589+
[last, first | _] = get_git_repo_revs("git_repo")
590+
591+
Process.put(:git_repo_opts, ref: first, depth: 1)
592+
593+
in_fixture("no_mixfile", fn ->
594+
Mix.Project.push(GitApp)
595+
596+
Mix.Tasks.Deps.Get.run([])
597+
message = "* Getting git_repo (#{fixture_path("git_repo")} - #{first})"
598+
assert_received {:mix_shell, :info, [^message]}
599+
assert_shallow("deps/git_repo", 1)
600+
assert File.read!("mix.lock") =~ first
601+
602+
# Change refspec
603+
update_dep(ref: last, depth: 1)
604+
Mix.Tasks.Deps.Get.run([])
605+
assert_shallow("deps/git_repo", 1)
606+
assert File.read!("mix.lock") =~ last
607+
end)
608+
end
609+
533610
test "removing depth retains shallow repository" do
534611
# For compatibility and simplicity, we follow Git's behavior and do not
535612
# attempt to unshallow an existing repository. This should not be a

0 commit comments

Comments
 (0)