Make `gn edit move` idempotent. `gn check --fix` currently outputs errors when it tries to apply the same move twice. This fixes move to be a no-op when it's already applied, similar to other commands. Change-Id: Id0b8182d3fc8e9f9d6d308951f387f766a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/26220 Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/edit_command_unittest.cc b/src/gn/edit_command_unittest.cc index 7673738..31276ea 100644 --- a/src/gn/edit_command_unittest.cc +++ b/src/gn/edit_command_unittest.cc
@@ -338,6 +338,20 @@ public_deps = [ "//a" ] } )")); + + // Moving a value that already exists in the destination attribute should be + // a no-op with no warnings. + EXPECT_SUCCESS(DoEdit("move deps public_deps //a", + R"( +executable("foo") { + public_deps = [ "//a" ] +} +)"), + Edited(R"( +executable("foo") { + public_deps = [ "//a" ] +} +)")); } TEST_F(EditCommandTest, NewSubcommand) {
diff --git a/src/gn/edit_subcommands.cc b/src/gn/edit_subcommands.cc index b2b7de4..9f6dd23 100644 --- a/src/gn/edit_subcommands.cc +++ b/src/gn/edit_subcommands.cc
@@ -117,7 +117,8 @@ bool RemoveFromTarget(const EditTarget& target, const std::string& attribute, const Value& value, - EditState& state) { + EditState& state, + bool warn_if_missing = true) { bool done = false; for (auto& assignment : target.assignments(attribute)) { auto matches = FindListElementInAssignment(target, assignment, value); @@ -156,7 +157,7 @@ } } } - } else if (target.is_explicit) { + } else if (target.is_explicit && warn_if_missing) { target.add_warning(state, "does not contain the value " + value.ToString(true) + " in attribute \"" + attribute + "\"."); @@ -269,6 +270,23 @@ }); } +// Returns whether |target| contains |value| in |attribute|. +// Assignments using `-=` are filtered out. +bool AttributeContainsValue(const EditTarget& target, + std::string_view attribute, + const Value& value) { + for (const auto& assignment : target.assignments(attribute)) { + if (const auto* op = assignment.node()->AsBinaryOp(); + op && op->op().type() == Token::MINUS_EQUALS) { + continue; + } + if (!FindListElementInAssignment(target, assignment, value).empty()) { + return true; + } + } + return false; +} + EditCommand MoveCommand(std::string from_attribute, std::string to_attribute, std::vector<Value> values) { @@ -279,7 +297,10 @@ EditState& state) -> Err { std::vector<Value> moved_values; for (const auto& value : values) { - if (RemoveFromTarget(target, from_attribute, value, state)) { + bool warn_if_missing = + !AttributeContainsValue(target, to_attribute, value); + if (RemoveFromTarget(target, from_attribute, value, state, + warn_if_missing)) { moved_values.push_back(value); } }