mirror of
https://github.com/esphome/esphome.git
synced 2026-05-21 11:02:18 +08:00
Merge remote-tracking branch 'origin/git_shallow_fetch' into integration
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
#include <csignal>
|
||||
#include <sched.h>
|
||||
#include <time.h>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace {
|
||||
@@ -22,9 +21,7 @@ void HOT yield() { ::sched_yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t ms = round(spec.tv_nsec / 1e6);
|
||||
return ((uint32_t) seconds) * 1000U + ms;
|
||||
return static_cast<uint32_t>(spec.tv_sec * 1000ULL + spec.tv_nsec / 1000000);
|
||||
}
|
||||
uint64_t millis_64() {
|
||||
struct timespec spec;
|
||||
@@ -43,9 +40,7 @@ void HOT delay(uint32_t ms) {
|
||||
uint32_t IRAM_ATTR HOT micros() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t us = round(spec.tv_nsec / 1e3);
|
||||
return ((uint32_t) seconds) * 1000000U + us;
|
||||
return static_cast<uint32_t>(spec.tv_sec * 1000000ULL + spec.tv_nsec / 1000);
|
||||
}
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
|
||||
struct timespec ts;
|
||||
|
||||
@@ -1,13 +1,73 @@
|
||||
#include "ir_rf_proxy.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::ir_rf_proxy {
|
||||
|
||||
static const char *const TAG = "ir_rf_proxy";
|
||||
|
||||
// ========== Shared transmit helper ==========
|
||||
// Static template: all instantiations occur in this translation unit.
|
||||
|
||||
template<typename CallT>
|
||||
static void transmit_raw_timings(remote_base::RemoteTransmitterBase *transmitter, uint32_t carrier_frequency,
|
||||
const CallT &call) {
|
||||
if (transmitter == nullptr) {
|
||||
ESP_LOGW(TAG, "No transmitter configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!call.has_raw_timings()) {
|
||||
ESP_LOGE(TAG, "No raw timings provided");
|
||||
return;
|
||||
}
|
||||
|
||||
auto transmit_call = transmitter->transmit();
|
||||
auto *transmit_data = transmit_call.get_data();
|
||||
transmit_data->set_carrier_frequency(carrier_frequency);
|
||||
|
||||
if (call.is_packed()) {
|
||||
transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(),
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(),
|
||||
call.get_repeat_count());
|
||||
} else if (call.is_base64url()) {
|
||||
if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) {
|
||||
ESP_LOGE(TAG, "Invalid base64url data");
|
||||
return;
|
||||
}
|
||||
constexpr int32_t max_timing_us = 500000;
|
||||
for (int32_t timing : transmit_data->get_data()) {
|
||||
int32_t abs_timing = timing < 0 ? -timing : timing;
|
||||
if (abs_timing > max_timing_us) {
|
||||
ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(),
|
||||
call.get_repeat_count());
|
||||
} else {
|
||||
transmit_data->set_data(call.get_raw_timings());
|
||||
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(),
|
||||
call.get_repeat_count());
|
||||
}
|
||||
|
||||
if (call.get_repeat_count() > 0) {
|
||||
transmit_call.set_send_times(call.get_repeat_count());
|
||||
}
|
||||
|
||||
transmit_call.perform();
|
||||
}
|
||||
|
||||
// ========== IrRfProxy (Infrared platform) ==========
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
|
||||
void IrRfProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"IR/RF Proxy '%s'\n"
|
||||
"IR Proxy '%s'\n"
|
||||
" Supports Transmitter: %s\n"
|
||||
" Supports Receiver: %s",
|
||||
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
|
||||
@@ -20,4 +80,54 @@ void IrRfProxy::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
void IrRfProxy::control(const infrared::InfraredCall &call) {
|
||||
uint32_t carrier = call.get_carrier_frequency().value_or(0);
|
||||
transmit_raw_timings(this->transmitter_, carrier, call);
|
||||
}
|
||||
|
||||
#endif // USE_IR_RF
|
||||
|
||||
// ========== RfProxy (Radio Frequency platform) ==========
|
||||
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
|
||||
void RfProxy::setup() {
|
||||
this->traits_.set_supports_transmitter(this->transmitter_ != nullptr);
|
||||
this->traits_.set_supports_receiver(this->receiver_ != nullptr);
|
||||
|
||||
// remote_transmitter/receiver always uses OOK (on-off keying)
|
||||
this->traits_.add_supported_modulation(radio_frequency::RadioFrequencyModulation::RADIO_FREQUENCY_MODULATION_OOK);
|
||||
|
||||
if (this->receiver_ != nullptr) {
|
||||
this->receiver_->register_listener(this);
|
||||
}
|
||||
}
|
||||
|
||||
void RfProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"RF Proxy '%s'\n"
|
||||
" Backend: remote_transmitter/receiver\n"
|
||||
" Supports Transmitter: %s\n"
|
||||
" Supports Receiver: %s",
|
||||
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
|
||||
YESNO(this->traits_.get_supports_receiver()));
|
||||
|
||||
const auto &traits = this->traits_;
|
||||
if (traits.get_frequency_min_hz() > 0) {
|
||||
if (traits.get_frequency_min_hz() == traits.get_frequency_max_hz()) {
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %.3f MHz (fixed)", traits.get_frequency_min_hz() / 1e6f);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Frequency Range: %.3f - %.3f MHz", traits.get_frequency_min_hz() / 1e6f,
|
||||
traits.get_frequency_max_hz() / 1e6f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RfProxy::control(const radio_frequency::RadioFrequencyCall &call) {
|
||||
// RF: no IR carrier modulation
|
||||
transmit_raw_timings(this->transmitter_, 0, call);
|
||||
}
|
||||
|
||||
#endif // USE_RADIO_FREQUENCY
|
||||
|
||||
} // namespace esphome::ir_rf_proxy
|
||||
|
||||
@@ -4,10 +4,19 @@
|
||||
// without following the normal breaking changes policy. Use at your own risk.
|
||||
// Once the API is considered stable, this warning will be removed.
|
||||
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
#include "esphome/components/infrared/infrared.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
#include "esphome/components/radio_frequency/radio_frequency.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::ir_rf_proxy {
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
/// IrRfProxy - Infrared platform implementation using remote_transmitter/receiver as backend
|
||||
class IrRfProxy : public infrared::Infrared {
|
||||
public:
|
||||
@@ -26,8 +35,36 @@ class IrRfProxy : public infrared::Infrared {
|
||||
void set_receiver_frequency(uint32_t frequency_hz) { this->get_traits().set_receiver_frequency_hz(frequency_hz); }
|
||||
|
||||
protected:
|
||||
void control(const infrared::InfraredCall &call) override;
|
||||
|
||||
// RF frequency in kHz (Hz / 1000); 0 = infrared, non-zero = RF
|
||||
uint32_t frequency_khz_{0};
|
||||
};
|
||||
#endif // USE_IR_RF
|
||||
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
/// RfProxy - Radio Frequency platform implementation using remote_transmitter/receiver as backend
|
||||
class RfProxy : public radio_frequency::RadioFrequency {
|
||||
public:
|
||||
RfProxy() = default;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
/// Set the remote transmitter component
|
||||
void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
|
||||
/// Set the remote receiver component
|
||||
void set_receiver(remote_base::RemoteReceiverBase *receiver) { this->receiver_ = receiver; }
|
||||
|
||||
/// Set the fixed carrier frequency in Hz (metadata: advertised via traits, does not tune hardware)
|
||||
void set_frequency_hz(uint32_t freq_hz) { this->traits_.set_fixed_frequency_hz(freq_hz); }
|
||||
|
||||
protected:
|
||||
void control(const radio_frequency::RadioFrequencyCall &call) override;
|
||||
|
||||
remote_base::RemoteTransmitterBase *transmitter_{nullptr};
|
||||
remote_base::RemoteReceiverBase *receiver_{nullptr};
|
||||
};
|
||||
#endif // USE_RADIO_FREQUENCY
|
||||
|
||||
} // namespace esphome::ir_rf_proxy
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Radio Frequency platform implementation using remote_base (remote_transmitter/receiver)."""
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import radio_frequency, remote_receiver, remote_transmitter
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID, ir_rf_proxy_ns
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
DEPENDENCIES = ["radio_frequency"]
|
||||
|
||||
RfProxy = ir_rf_proxy_ns.class_("RfProxy", radio_frequency.RadioFrequency)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
radio_frequency.radio_frequency_schema(RfProxy).extend(
|
||||
{
|
||||
cv.Optional(CONF_FREQUENCY): cv.frequency,
|
||||
cv.Optional(CONF_REMOTE_RECEIVER_ID): cv.use_id(
|
||||
remote_receiver.RemoteReceiverComponent
|
||||
),
|
||||
cv.Optional(CONF_REMOTE_TRANSMITTER_ID): cv.use_id(
|
||||
remote_transmitter.RemoteTransmitterComponent
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID),
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Validate that RF transmitters have carrier duty set to 100%."""
|
||||
if CONF_REMOTE_TRANSMITTER_ID not in config:
|
||||
return
|
||||
|
||||
transmitter_id = config[CONF_REMOTE_TRANSMITTER_ID]
|
||||
full_config = fv.full_config.get()
|
||||
transmitter_path = full_config.get_path_for_id(transmitter_id)[:-1]
|
||||
transmitter_config = full_config.get_config_for_path(transmitter_path)
|
||||
|
||||
duty_percent = transmitter_config.get(CONF_CARRIER_DUTY_PERCENT)
|
||||
if duty_percent is not None and duty_percent != 100:
|
||||
raise cv.Invalid(
|
||||
f"Transmitter '{transmitter_id}' must have '{CONF_CARRIER_DUTY_PERCENT}' "
|
||||
"set to 100% for RF transmission. Dedicated RF hardware handles modulation; "
|
||||
"applying a carrier duty cycle would corrupt the signal"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
"""Code generation for remote_base radio frequency platform."""
|
||||
var = await radio_frequency.new_radio_frequency(config)
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
cg.add(var.set_frequency_hz(int(config[CONF_FREQUENCY])))
|
||||
|
||||
if CONF_REMOTE_TRANSMITTER_ID in config:
|
||||
transmitter = await cg.get_variable(config[CONF_REMOTE_TRANSMITTER_ID])
|
||||
cg.add(var.set_transmitter(transmitter))
|
||||
|
||||
if CONF_REMOTE_RECEIVER_ID in config:
|
||||
receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID])
|
||||
cg.add(var.set_receiver(receiver))
|
||||
+15
-5
@@ -128,7 +128,10 @@ def clone_or_update(
|
||||
# We need to fetch the PR branch first, otherwise git will complain
|
||||
# about missing objects
|
||||
_LOGGER.info("Fetching %s", ref)
|
||||
run_git_command(["git", "fetch", "--", "origin", ref], git_dir=repo_dir)
|
||||
run_git_command(
|
||||
["git", "fetch", "--depth=1", "--", "origin", ref],
|
||||
git_dir=repo_dir,
|
||||
)
|
||||
run_git_command(
|
||||
["git", "reset", "--hard", "FETCH_HEAD"], git_dir=repo_dir
|
||||
)
|
||||
@@ -138,7 +141,8 @@ def clone_or_update(
|
||||
"Initializing submodules (%s) for %s", ", ".join(submodules), key
|
||||
)
|
||||
run_git_command(
|
||||
["git", "submodule", "update", "--init"] + submodules,
|
||||
["git", "submodule", "update", "--init", "--depth=1", "--"]
|
||||
+ submodules,
|
||||
git_dir=repo_dir,
|
||||
)
|
||||
except GitException:
|
||||
@@ -181,8 +185,13 @@ def clone_or_update(
|
||||
git_dir=repo_dir,
|
||||
)
|
||||
|
||||
# Fetch remote ref
|
||||
cmd = ["git", "fetch", "--", "origin"]
|
||||
# Fetch from the remote. --depth=1 keeps the clone shallow
|
||||
# while still picking up new commits when the remote tip
|
||||
# moves: a shallow fetch retrieves the current tip being
|
||||
# fetched, whether that's an explicit ref or the remote's
|
||||
# default branch, then reset --hard FETCH_HEAD updates the
|
||||
# working tree to it.
|
||||
cmd = ["git", "fetch", "--depth=1", "--", "origin"]
|
||||
if ref is not None:
|
||||
cmd.append(ref)
|
||||
run_git_command(cmd, git_dir=repo_dir)
|
||||
@@ -231,7 +240,8 @@ def clone_or_update(
|
||||
"Updating submodules (%s) for %s", ", ".join(submodules), key
|
||||
)
|
||||
run_git_command(
|
||||
["git", "submodule", "update", "--init"] + submodules,
|
||||
["git", "submodule", "update", "--init", "--depth=1", "--"]
|
||||
+ submodules,
|
||||
git_dir=repo_dir,
|
||||
)
|
||||
|
||||
|
||||
@@ -402,8 +402,11 @@ def should_run_benchmarks(branch: str | None = None) -> bool:
|
||||
Benchmarks run when any of the following conditions are met:
|
||||
|
||||
1. Core C++ files changed (esphome/core/*)
|
||||
2. A directly changed component has benchmark files (no dependency expansion)
|
||||
3. Benchmark infrastructure changed (tests/benchmarks/*, script/cpp_benchmark.py,
|
||||
2. The host platform changed (esphome/components/host/*) — benchmarks
|
||||
are built and run on the host platform, so its implementations of
|
||||
``millis()``/``micros()``/etc. affect every benchmark
|
||||
3. A directly changed component has benchmark files (no dependency expansion)
|
||||
4. Benchmark infrastructure changed (tests/benchmarks/*, script/cpp_benchmark.py,
|
||||
script/build_helpers.py, script/setup_codspeed_lib.py)
|
||||
|
||||
Unlike unit tests, benchmarks do NOT expand to dependent components.
|
||||
@@ -420,6 +423,10 @@ def should_run_benchmarks(branch: str | None = None) -> bool:
|
||||
if core_changed(files):
|
||||
return True
|
||||
|
||||
# Host platform supplies the runtime that benchmarks execute on
|
||||
if any(f.startswith("esphome/components/host/") for f in files):
|
||||
return True
|
||||
|
||||
# Check if benchmark infrastructure changed
|
||||
if any(
|
||||
f.startswith("tests/benchmarks/") or f in BENCHMARK_INFRASTRUCTURE_FILES
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
remote_receiver:
|
||||
id: rf_receiver
|
||||
pin: ${rx_pin}
|
||||
|
||||
# Test radio_frequency platform with receiver
|
||||
radio_frequency:
|
||||
# RF 900MHz receiver
|
||||
- platform: ir_rf_proxy
|
||||
id: rf_900_rx
|
||||
name: "RF 900 Receiver"
|
||||
frequency: 900 MHz
|
||||
remote_receiver_id: rf_receiver
|
||||
|
||||
# RF receiver (no frequency specified)
|
||||
- platform: ir_rf_proxy
|
||||
id: rf_rx
|
||||
name: "RF Receiver"
|
||||
remote_receiver_id: rf_receiver
|
||||
@@ -0,0 +1,19 @@
|
||||
remote_transmitter:
|
||||
id: rf_transmitter
|
||||
pin: ${tx_pin}
|
||||
carrier_duty_percent: 100%
|
||||
|
||||
# Test radio_frequency platform with transmitter
|
||||
radio_frequency:
|
||||
# RF 433MHz transmitter
|
||||
- platform: ir_rf_proxy
|
||||
id: rf_433_tx
|
||||
name: "RF 433 Transmitter"
|
||||
frequency: 433 MHz
|
||||
remote_transmitter_id: rf_transmitter
|
||||
|
||||
# RF transmitter (no frequency specified)
|
||||
- platform: ir_rf_proxy
|
||||
id: rf_tx
|
||||
name: "RF Transmitter"
|
||||
remote_transmitter_id: rf_transmitter
|
||||
@@ -0,0 +1,7 @@
|
||||
network:
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
api:
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
rx: !include common-rx.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
rx: !include common-rx.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
rx: !include common-rx.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
rx: !include common-rx.yaml
|
||||
tx: !include common-tx.yaml
|
||||
@@ -1842,6 +1842,22 @@ def test_should_run_benchmarks_core_header_change() -> None:
|
||||
assert determine_jobs.should_run_benchmarks() is True
|
||||
|
||||
|
||||
def test_should_run_benchmarks_host_platform_change() -> None:
|
||||
"""Test benchmarks trigger on host platform changes.
|
||||
|
||||
Benchmarks build and run on the host platform, so changes to its
|
||||
millis()/micros()/etc. implementations affect every benchmark.
|
||||
"""
|
||||
for host_file in [
|
||||
"esphome/components/host/core.cpp",
|
||||
"esphome/components/host/__init__.py",
|
||||
]:
|
||||
with patch.object(determine_jobs, "changed_files", return_value=[host_file]):
|
||||
assert determine_jobs.should_run_benchmarks() is True, (
|
||||
f"Expected benchmarks to run for {host_file}"
|
||||
)
|
||||
|
||||
|
||||
def test_should_run_benchmarks_benchmark_infra_change() -> None:
|
||||
"""Test benchmarks trigger on benchmark infrastructure changes."""
|
||||
for infra_file in [
|
||||
|
||||
@@ -782,3 +782,193 @@ def test_clone_or_update_stale_clone_is_retried_after_cleanup(
|
||||
assert repo_dir.exists()
|
||||
assert call_count["clone"] == 2
|
||||
assert call_count["fetch"] == 2
|
||||
|
||||
|
||||
def test_clone_with_ref_uses_shallow_fetch(
|
||||
tmp_path: Path, mock_run_git_command: Mock
|
||||
) -> None:
|
||||
"""Clone with a ref should use --depth=1 on both clone and fetch."""
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
|
||||
url = "https://github.com/test/repo"
|
||||
ref = "pull/123/head"
|
||||
domain = "test"
|
||||
repo_dir = _compute_repo_dir(url, ref, domain)
|
||||
|
||||
def git_command_side_effect(
|
||||
cmd: list[str], cwd: str | None = None, **kwargs: Any
|
||||
) -> str:
|
||||
if _get_git_command_type(cmd) == "clone":
|
||||
repo_dir.mkdir(parents=True, exist_ok=True)
|
||||
(repo_dir / ".git").mkdir(exist_ok=True)
|
||||
return ""
|
||||
|
||||
mock_run_git_command.side_effect = git_command_side_effect
|
||||
|
||||
git.clone_or_update(url=url, ref=ref, refresh=None, domain=domain)
|
||||
|
||||
call_list = mock_run_git_command.call_args_list
|
||||
|
||||
clone_calls = [c for c in call_list if "clone" in c[0][0]]
|
||||
assert len(clone_calls) == 1
|
||||
assert "--depth=1" in clone_calls[0][0][0]
|
||||
|
||||
fetch_calls = [c for c in call_list if "fetch" in c[0][0]]
|
||||
assert len(fetch_calls) == 1
|
||||
assert "--depth=1" in fetch_calls[0][0][0]
|
||||
# Ref must still be passed so the requested commit/branch is fetched.
|
||||
assert ref in fetch_calls[0][0][0]
|
||||
|
||||
|
||||
def test_clone_with_submodules_uses_shallow_submodule_update(
|
||||
tmp_path: Path, mock_run_git_command: Mock
|
||||
) -> None:
|
||||
"""Submodule init on a fresh clone should use --depth=1."""
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
|
||||
url = "https://github.com/test/repo"
|
||||
domain = "test"
|
||||
repo_dir = _compute_repo_dir(url, None, domain)
|
||||
|
||||
def git_command_side_effect(
|
||||
cmd: list[str], cwd: str | None = None, **kwargs: Any
|
||||
) -> str:
|
||||
if _get_git_command_type(cmd) == "clone":
|
||||
repo_dir.mkdir(parents=True, exist_ok=True)
|
||||
(repo_dir / ".git").mkdir(exist_ok=True)
|
||||
return ""
|
||||
|
||||
mock_run_git_command.side_effect = git_command_side_effect
|
||||
|
||||
git.clone_or_update(
|
||||
url=url,
|
||||
ref=None,
|
||||
refresh=None,
|
||||
domain=domain,
|
||||
submodules=["components/foo"],
|
||||
)
|
||||
|
||||
submodule_calls = [
|
||||
c for c in mock_run_git_command.call_args_list if "submodule" in c[0][0]
|
||||
]
|
||||
assert len(submodule_calls) == 1
|
||||
cmd = submodule_calls[0][0][0]
|
||||
assert "--depth=1" in cmd
|
||||
assert "components/foo" in cmd
|
||||
# The `--` terminator must precede the submodule paths so a path
|
||||
# beginning with `-` cannot be parsed as an option.
|
||||
assert cmd.index("--") < cmd.index("components/foo")
|
||||
|
||||
|
||||
def test_refresh_fetch_is_shallow(tmp_path: Path, mock_run_git_command: Mock) -> None:
|
||||
"""The refresh-path fetch should use --depth=1."""
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
|
||||
url = "https://github.com/test/repo"
|
||||
ref = "main"
|
||||
domain = "test"
|
||||
repo_dir = _compute_repo_dir(url, ref, domain)
|
||||
|
||||
_setup_old_repo(repo_dir)
|
||||
mock_run_git_command.return_value = "abc123"
|
||||
|
||||
git.clone_or_update(
|
||||
url=url, ref=ref, refresh=TimePeriodSeconds(days=1), domain=domain
|
||||
)
|
||||
|
||||
fetch_calls = [c for c in mock_run_git_command.call_args_list if "fetch" in c[0][0]]
|
||||
assert len(fetch_calls) == 1
|
||||
cmd = fetch_calls[0][0][0]
|
||||
assert "--depth=1" in cmd
|
||||
# Ref must still be in the refresh fetch so the right tip is updated.
|
||||
assert cmd[-1] == ref
|
||||
|
||||
|
||||
def test_refresh_submodule_update_is_shallow(
|
||||
tmp_path: Path, mock_run_git_command: Mock
|
||||
) -> None:
|
||||
"""The refresh-path submodule update should use --depth=1."""
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
|
||||
url = "https://github.com/test/repo"
|
||||
domain = "test"
|
||||
repo_dir = _compute_repo_dir(url, None, domain)
|
||||
|
||||
_setup_old_repo(repo_dir)
|
||||
mock_run_git_command.return_value = "abc123"
|
||||
|
||||
git.clone_or_update(
|
||||
url=url,
|
||||
ref=None,
|
||||
refresh=TimePeriodSeconds(days=1),
|
||||
domain=domain,
|
||||
submodules=["components/foo"],
|
||||
)
|
||||
|
||||
submodule_calls = [
|
||||
c for c in mock_run_git_command.call_args_list if "submodule" in c[0][0]
|
||||
]
|
||||
assert len(submodule_calls) == 1
|
||||
cmd = submodule_calls[0][0][0]
|
||||
assert "--depth=1" in cmd
|
||||
assert "components/foo" in cmd
|
||||
assert cmd.index("--") < cmd.index("components/foo")
|
||||
|
||||
|
||||
def test_refresh_picks_up_new_remote_commits(
|
||||
tmp_path: Path, mock_run_git_command: Mock
|
||||
) -> None:
|
||||
"""Shallow fetch must still pull new commits when the remote tip moves.
|
||||
|
||||
Simulates a stale local repo at SHA "old" while the remote has advanced
|
||||
to SHA "new". The refresh path must run fetch (with --depth=1) followed
|
||||
by reset --hard FETCH_HEAD so the working tree advances to the new tip.
|
||||
"""
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
|
||||
url = "https://github.com/test/repo"
|
||||
ref = "main"
|
||||
domain = "test"
|
||||
repo_dir = _compute_repo_dir(url, ref, domain)
|
||||
|
||||
_setup_old_repo(repo_dir)
|
||||
|
||||
# rev-parse is called once before fetch to record the pre-update SHA.
|
||||
rev_parse_calls = {"count": 0}
|
||||
|
||||
def git_command_side_effect(
|
||||
cmd: list[str], cwd: str | None = None, **kwargs: Any
|
||||
) -> str:
|
||||
cmd_type = _get_git_command_type(cmd)
|
||||
if cmd_type == "rev-parse":
|
||||
rev_parse_calls["count"] += 1
|
||||
return "old_sha"
|
||||
return ""
|
||||
|
||||
mock_run_git_command.side_effect = git_command_side_effect
|
||||
|
||||
_, revert = git.clone_or_update(
|
||||
url=url, ref=ref, refresh=TimePeriodSeconds(days=1), domain=domain
|
||||
)
|
||||
|
||||
# Verify the refresh sequence: rev-parse -> stash -> fetch (depth=1) -> reset
|
||||
call_list = mock_run_git_command.call_args_list
|
||||
cmd_sequence = [_get_git_command_type(c[0][0]) for c in call_list]
|
||||
assert cmd_sequence == ["rev-parse", "stash", "fetch", "reset"]
|
||||
|
||||
fetch_cmd = call_list[2][0][0]
|
||||
assert "--depth=1" in fetch_cmd
|
||||
assert fetch_cmd[-1] == ref
|
||||
|
||||
reset_cmd = call_list[3][0][0]
|
||||
assert reset_cmd[-1] == "FETCH_HEAD"
|
||||
|
||||
# revert callback should reset back to the recorded pre-update SHA.
|
||||
assert revert is not None
|
||||
revert()
|
||||
assert mock_run_git_command.call_args_list[-1][0][0] == [
|
||||
"git",
|
||||
"reset",
|
||||
"--hard",
|
||||
"old_sha",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user