Skip to content

Commit

Permalink
add new clean command
Browse files Browse the repository at this point in the history
Rebasing lots of branches can also result in lots of loose objects. This
command will run `git gc` as well as cleaning up branches that are no
longer needed. Branches that have been merged into the root branch and
remote branches that are no longer present.
  • Loading branch information
mockdeep committed Sep 7, 2024
1 parent 016606d commit eb6b5b5
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 11 deletions.
1 change: 1 addition & 0 deletions lib/baes/actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Baes::Actions; end

require_relative "actions/bisect"
require_relative "actions/build_tree"
require_relative "actions/clean"
require_relative "actions/load_configuration"
require_relative "actions/load_rebase_configuration"
require_relative "actions/rebase"
Expand Down
27 changes: 27 additions & 0 deletions lib/baes/actions/clean.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

# class that will prune remote branches, garbage collect, and delete merged
# branches
module Baes::Actions::Clean
class << self
include Baes::Configuration::Helpers

# run the command
def call
output.puts("cleaning up branches")
git.checkout(root_name)
git.remote_prune("origin")
git.gc
git.delete_branches(merged_branches)
end

private

def merged_branches
branches = git.branch_names("--merged")
branches.select do |branch|
branch != root_name && !ignored_branch_names.include?(branch)
end
end
end
end
2 changes: 2 additions & 0 deletions lib/baes/actions/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def call(options)
Baes::Actions::LoadRebaseConfiguration.call(options)

Baes::Actions::Rebase.call
when "clean"
Baes::Actions::Clean.call
when nil
Baes::Actions::LoadConfiguration.call(["-h"])
when /^-/
Expand Down
28 changes: 26 additions & 2 deletions lib/baes/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def current_branch_name
end

# list branch names and raise on failure
def branch_names
stdout = run_or_raise("git branch")
def branch_names(cli_args = "")
stdout = run_or_raise("git branch #{cli_args}".strip)

stdout.lines.map { |line| line.sub(/^\*/, "").strip }
end
Expand Down Expand Up @@ -57,6 +57,30 @@ def last_rebase_step
end
end

# prune remote branches and raise on failure
def remote_prune(remote)
output.puts("pruning remote branches for #{remote}")
stdout = run_or_raise("git remote prune #{remote}")

output.puts(stdout) unless stdout.empty?
end

# garbage collect and raise on failure
def gc
output.puts("garbage collecting")
stdout = run_or_raise("git gc --prune=now")

output.puts(stdout) unless stdout.empty?
end

# delete branches and raise on failure
def delete_branches(branch_names)
return if branch_names.empty?

output.puts("deleting branches: #{branch_names.join(", ")}")
output.puts(run_or_raise("git branch -d #{branch_names.join(" ")}"))
end

private

def run_or_raise(command)
Expand Down
13 changes: 13 additions & 0 deletions spec/baes/actions/clean_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

RSpec.describe Baes::Actions::Clean do
describe "#call" do
it "calls git gc" do
FakeGit.branch_names = ["main", "my_branch"]

described_class.call

expect(FakeGit.gc_called).to be(true)
end
end
end
8 changes: 8 additions & 0 deletions spec/baes/actions/run_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ def stub3(command, stdout: "", stderr: "", success: true)
expect(FakeGit.rebases).to eq([["my_branch", "main"]])
end

it "cleans when given the clean command" do
FakeGit.branch_names = ["main", "my_branch"]

described_class.call(["clean"])

expect(FakeGit.gc_called).to be(true)
end

it "raises an error when given an invalid command" do
expect { described_class.call(["foo"]) }
.to raise_error(SystemExit)
Expand Down
66 changes: 59 additions & 7 deletions spec/baes/git_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def run_and_rescue
nil
end

describe "#checkout" do
describe ".checkout" do
it "prints stdout" do
stub3("git checkout my_branch", stdout: "out")

Expand All @@ -42,7 +42,7 @@ def run_and_rescue
end
end

describe "#rebase" do
describe ".rebase" do
it "prints stdout" do
stub3("git rebase my_branch", stdout: "out")

Expand All @@ -68,7 +68,7 @@ def run_and_rescue
end
end

describe "#current_branch_name" do
describe ".current_branch_name" do
context "when command is not successful" do
it "prints stderr" do
command = "git rev-parse --abbrev-ref HEAD"
Expand Down Expand Up @@ -96,7 +96,7 @@ def run_and_rescue
end
end

describe "#branch_names" do
describe ".branch_names" do
context "when command is not successful" do
it "prints stderr" do
stub3("git branch", stderr: "error", success: false)
Expand All @@ -123,7 +123,7 @@ def run_and_rescue
end
end

describe "#rebase_skip" do
describe ".rebase_skip" do
it "prints stdout" do
stub3("git rebase --skip", stdout: "out")

Expand All @@ -149,7 +149,7 @@ def run_and_rescue
end
end

describe "#next_rebase_step" do
describe ".next_rebase_step" do
it "returns the contents of the next rebase file when rebase-apply" do
path = "./.git/rebase-apply"
expect(Dir).to receive(:exist?).with(path).and_return(true)
Expand All @@ -168,7 +168,59 @@ def run_and_rescue
end
end

describe "#last_rebase_step" do
describe ".remote_prune" do
it "prints stdout" do
stub3("git remote prune origin", stdout: "out")

described_class.remote_prune("origin")

expect(output.string).to eq("pruning remote branches for origin\nout\n")
end

it "does not print stdout when empty" do
stub3("git remote prune origin", stdout: "")

described_class.remote_prune("origin")

expect(output.string).to eq("pruning remote branches for origin\n")
end
end

describe ".gc" do
it "prints stdout" do
stub3("git gc --prune=now", stdout: "out")

described_class.gc

expect(output.string).to eq("garbage collecting\nout\n")
end

it "does not print stdout when empty" do
stub3("git gc --prune=now", stdout: "")

described_class.gc

expect(output.string).to eq("garbage collecting\n")
end
end

describe ".delete_branches" do
it "prints stdout" do
stub3("git branch -d branch1 branch2", stdout: "out")

described_class.delete_branches(["branch1", "branch2"])

expect(output.string).to eq("deleting branches: branch1, branch2\nout\n")
end

it "returns early when branch_names is empty" do
expect(Open3).not_to receive(:capture3)

described_class.delete_branches([])
end
end

describe ".last_rebase_step" do
it "returns the contents of the last rebase file when rebase-apply" do
path = "./.git/rebase-apply"
expect(Dir).to receive(:exist?).with(path).and_return(true)
Expand Down
12 changes: 10 additions & 2 deletions spec/support/fake_git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def rebase(base_branch_name)
FakeStatus.new(success: next_success)
end

def branch_names
def branch_names(*)
@branch_names ||= []
end

Expand Down Expand Up @@ -61,14 +61,22 @@ def rebase_index
@rebase_index ||= 0
end

attr_reader :gc_called
attr_writer :rebase_index, :branch_names, :rebases_successful

attr_accessor :current_branch_name

def rebases_successful
@rebases_successful ||= []
end

def remote_prune(_); end

def gc
@gc_called = true
end

def delete_branches(branch_names); end

def reset
instance_variables.each do |ivar|
remove_instance_variable(ivar)
Expand Down

0 comments on commit eb6b5b5

Please sign in to comment.