Skip to content

Commit

Permalink
add new clean command (#97)
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 authored Sep 7, 2024
1 parent 016606d commit fbe48df
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 fbe48df

Please sign in to comment.