Skip to content

Commit

Permalink
Merge pull request #6693 from dependabot/bdragon/shared-workspace
Browse files Browse the repository at this point in the history
Add workspace experiment to maintain state between updates and capture success/failure of each
  • Loading branch information
bdragon authored Jun 20, 2023
2 parents 03f55a6 + a7c49ba commit d7fa9fb
Show file tree
Hide file tree
Showing 12 changed files with 507 additions and 9 deletions.
9 changes: 7 additions & 2 deletions common/lib/dependabot/shared_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require "dependabot/simple_instrumentor"
require "dependabot/utils"
require "dependabot/errors"
require "dependabot/workspace"
require "dependabot"

module Dependabot
Expand All @@ -27,12 +28,16 @@ def self.in_a_temporary_repo_directory(directory = "/",
repo_contents_path = nil,
&block)
if repo_contents_path
path = Pathname.new(File.join(repo_contents_path, directory)).
expand_path
path = Pathname.new(File.join(repo_contents_path, directory)).expand_path
reset_git_repo(repo_contents_path)
# Handle missing directories by creating an empty one and relying on the
# file fetcher to raise a DependencyFileNotFound error
FileUtils.mkdir_p(path)

if (workspace = Dependabot::Workspace.active_workspace)
return workspace.change(&block)
end

Dir.chdir(path) { yield(path) }
else
in_a_temporary_directory(directory, &block)
Expand Down
13 changes: 13 additions & 0 deletions common/lib/dependabot/workspace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

require "dependabot/workspace/git"

module Dependabot
module Workspace
@active_workspace = nil

class << self
attr_accessor :active_workspace
end
end
end
50 changes: 50 additions & 0 deletions common/lib/dependabot/workspace/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

module Dependabot
module Workspace
class Base
attr_reader :change_attempts, :path

def initialize(path)
@path = path
@change_attempts = []
end

def changed?
changes.any?
end

def changes
change_attempts.select(&:success?)
end

def failed_change_attempts
change_attempts.select(&:error?)
end

def change(memo = nil)
change_attempt = nil
Dir.chdir(path) { yield(path) }
change_attempt = capture_change(memo)
rescue StandardError => e
change_attempt = capture_failed_change_attempt(memo, e)
raise e
ensure
change_attempts << change_attempt unless change_attempt.nil?
clean
end

def to_patch
""
end

def reset!; end

protected

def capture_change(memo = nil); end

def capture_failed_change_attempt(memo = nil, error = nil); end
end
end
end
25 changes: 25 additions & 0 deletions common/lib/dependabot/workspace/change_attempt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Dependabot
module Workspace
class ChangeAttempt
attr_reader :diff, :error, :id, :memo, :workspace

def initialize(workspace, id:, memo:, diff: nil, error: nil)
@workspace = workspace
@id = id
@memo = memo
@diff = diff
@error = error
end

def success?
error.nil?
end

def error?
error
end
end
end
end
92 changes: 92 additions & 0 deletions common/lib/dependabot/workspace/git.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

require "dependabot/workspace/base"
require "dependabot/workspace/change_attempt"

module Dependabot
module Workspace
class Git < Base
attr_reader :initial_head_sha

def initialize(repo_contents_path, directory = "/")
super(Pathname.new(File.join(repo_contents_path, directory)).expand_path)
@initial_head_sha = head_sha
end

def to_patch
run_shell_command("git diff --patch #{@initial_head_sha}.. .")
end

def reset!
reset(initial_head_sha)
clean
run_shell_command("git stash clear")
@change_attempts = []

nil
end

protected

def capture_change(memo = nil)
changed_files = run_shell_command("git status --short .").strip
return nil if changed_files.empty?

sha, diff = commit(memo)
ChangeAttempt.new(self, id: sha, memo: memo, diff: diff)
end

def capture_failed_change_attempt(memo = nil, error = nil)
changed_files =
run_shell_command("git status --untracked-files=all --ignored=matching --short .").strip
return nil if changed_files.nil? && error.nil?

sha, diff = stash(memo)
ChangeAttempt.new(self, id: sha, memo: memo, diff: diff, error: error)
end

private

def head_sha
run_shell_command("git rev-parse HEAD").strip
end

def last_stash_sha
run_shell_command("git rev-parse refs/stash").strip
end

def stash(memo = nil)
msg = memo || "workspace change attempt"
run_shell_command("git add --all --force .")
run_shell_command(%(git stash push --all -m "#{msg}"), allow_unsafe_shell_command: true)

sha = last_stash_sha
diff = run_shell_command("git stash show --patch #{sha}")

[sha, diff]
end

def commit(memo = nil)
run_shell_command("git add #{path}")
diff = run_shell_command("git diff --cached .")

msg = memo || "workspace change"
run_shell_command(%(git commit -m "#{msg}"), allow_unsafe_shell_command: true)

[head_sha, diff]
end

def reset(sha)
run_shell_command("git reset --hard #{sha}")
end

def clean
run_shell_command("git clean -fx .")
end

def run_shell_command(*args, **kwargs)
Dir.chdir(path) { SharedHelpers.run_shell_command(*args, **kwargs) }
end
end
end
end
26 changes: 26 additions & 0 deletions common/spec/dependabot/shared_helpers_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# frozen_string_literal: true

require "fileutils"
require "tmpdir"

require "spec_helper"
require "dependabot/shared_helpers"
require "dependabot/simple_instrumentor"
require "dependabot/workspace"

RSpec.describe Dependabot::SharedHelpers do
let(:spec_root) { File.join(File.dirname(__FILE__), "..") }
Expand Down Expand Up @@ -44,6 +48,10 @@ def existing_tmp_folders
let(:project_name) { "vendor_gems" }
let(:repo_contents_path) { build_tmp_repo(project_name) }

after do
FileUtils.rm_rf(repo_contents_path)
end

it "runs inside the temporary repo directory" do
expect(in_a_temporary_repo_directory).to eq(repo_contents_path)
end
Expand Down Expand Up @@ -94,6 +102,24 @@ def existing_tmp_folders
expect(described_class).to have_received(:in_a_temporary_directory)
end
end

context "when there is an active workspace" do
let(:project_name) { "simple" }
let(:repo_contents_path) { build_tmp_repo(project_name, tmp_dir_path: Dir.tmpdir) }
let(:workspace) { Dependabot::Workspace::Git.new(repo_contents_path, directory) }

before do
allow(Dependabot::Workspace).to receive(:active_workspace).and_return(workspace)
end

it "delegates to the workspace" do
expect(workspace).to receive(:change).and_call_original

expect do |b|
described_class.in_a_temporary_repo_directory(directory, repo_contents_path, &b)
end.to yield_with_args(Pathname)
end
end
end

describe ".run_helper_subprocess" do
Expand Down
Loading

0 comments on commit d7fa9fb

Please sign in to comment.