Class: Agent::Lock::Tree

Inherits:
Object
  • Object
show all
Defined in:
lib/agent/lock/tree.rb

Overview

The working tree a lock is about, and where its locks are kept.

Locks live in <git-common-dir>/agent-locks, which is chosen rather than ~/.agent-locks or a dotfile at the root for three reasons. Git cannot track anything inside .git, so no repository needs a .gitignore line and git clean -xdf cannot wipe the locks. Every worktree of a repository resolves --git-common-dir to the same directory, so one store serves all of them. And it dies with the checkout, instead of outliving it in a home directory nobody thinks to sweep.

Outside a repository the store falls back to ~/.agent-locks, keyed by a digest of the tree, since there is no .git to hide in.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dir = Dir.pwd) ⇒ Tree

Returns a new instance of Tree.



31
32
33
34
35
# File 'lib/agent/lock/tree.rb', line 31

def initialize(dir = Dir.pwd)
  @dir = File.realpath(dir)
  @root = git("rev-parse", "--show-toplevel") || @dir
  @root = File.realpath(@root)
end

Instance Attribute Details

#root ⇒ String (readonly)

Returns absolute, symlinks resolved, so /tmp and /private/tmp cannot become two names for one tree.

Returns:

  • (String) —

    absolute, symlinks resolved, so /tmp and /private/tmp cannot become two names for one tree



29
30
31
# File 'lib/agent/lock/tree.rb', line 29

def root
  @root
end

Class Method Details

.for(dir = Dir.pwd) ⇒ Tree

Parameters:

  • dir (String) (defaults to: Dir.pwd) —

    anywhere inside the tree

Returns:



24
# File 'lib/agent/lock/tree.rb', line 24

def for(dir = Dir.pwd) = new(dir)

Instance Method Details

#relative(path) ⇒ String

A path the user typed, read from where they stand, as a name inside this tree.

There used to be a fallback to the basename for anything outside the root, so /etc/passwd quietly locked the tree's own passwd: an agent was told it held something it had never asked for, and the thing it had asked for stayed unguarded. Outside is now an error.

The path is compared as typed first, then with symlinks resolved, so an alias of the tree (/tmp for /private/tmp) is still inside it, while an in-tree symlink that points elsewhere keeps its in-tree name, which is what the lock is about. The path need not exist: an agent claims a file before writing it.

Parameters:

  • path (String) —

    anything the user typed, absolute or relative

Returns:

  • (String) —

    that path relative to the root, "." for the root itself

Raises:



69
70
71
72
73
# File 'lib/agent/lock/tree.rb', line 69

def relative(path)
  absolute = File.absolute_path(path, @dir)
  inside(absolute) || inside(resolve(absolute)) ||
    raise(Scope::Invalid, "#{path} is outside the tree #{root}")
end

#store_dir ⇒ String

$AGENT_LOCK_DIR environment variable if defined, or 'agent-locks' inside .git if available, or the ~/.agent-locks/ in user's home folder.

Returns:

  • (String) —

    where locks for this tree are written



45
46
47
48
49
50
# File 'lib/agent/lock/tree.rb', line 45

def store_dir
  return File.expand_path(ENV["AGENT_LOCK_DIR"]) if ENV["AGENT_LOCK_DIR"]
  return File.join(common_dir, "agent-locks") if common_dir

  File.join(Dir.home, ".agent-locks", ::Digest::SHA256.hexdigest(root)[0, 12])
end

#worktree? ⇒ Boolean

Returns a linked worktree rather than the original checkout.

Returns:

  • (Boolean) —

    a linked worktree rather than the original checkout



38
# File 'lib/agent/lock/tree.rb', line 38

def worktree? = !git_dir.nil? && git_dir != common_dir