Class: Git::Commands::Arguments Private

Inherits:
Object
  • Object
show all
Defined in:
lib/git/commands/arguments.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

This class provides a DSL for mapping Ruby method arguments to git command-line arguments.

Overview

This class provides a DSL for defining how arguments passed to #bind should be mapped to git CLI argument arrays. The process follows four phases:

  1. Definition of expected CLI arguments and their constraints
  2. Binding of method arguments to the definition
  3. Validation of values against argument constraints
  4. Building of the CLI argument array

See Init for a usage example.

Example: Defining arguments for a command

# 1. Definition of expected CLI arguments and their constraints
args_def = Arguments.define do
  flag_option :force
  value_option :branch
  operand :repository, required: true
end

# 2. Binding of method arguments to the definition
# 3. Validation of values against argument constraints
args = args_def.bind('https://github.com/user/repo', force: true, branch: 'main')

# 4. Building of the CLI argument array
args.to_a # => ['--force', '--branch', 'main', 'https://github.com/user/repo']

# Bonus: accessing bound values
args.force?      # => true
args.branch      # => 'main'
args.repository  # => 'https://github.com/user/repo'

Terminology

This class bridges CLI and Ruby interfaces. While both use the term "arguments" for values passed to commands/methods, they differ in terminology for specific argument types:

CLI (POSIX) Ruby Interface Description
argument specification DSL definition Declared command inputs and constraints
arguments arguments Values passed when calling a command/method
operands positional arguments Arguments identified by position
options keyword arguments Arguments identified by name (--force / force:)

The following sections explain each interface in detail.

CLI Interface (POSIX)

An argument specification declares what command inputs are accepted and their constraints.

Example:

git branch (--set-upstream-to=<upstream>|-u <upstream>) [<branch-name>]

When a command is invoked, arguments are the values passed to it:

  • Arguments: Values passed when calling the command (everything after the command name)
  • Operands: Arguments identified by position
  • Options: Arguments identified by name (prefixed with - or --)

Example:

git branch --set-upstream-to=origin/main main
  • Operands: main
  • Options: --set-upstream-to=origin/main

Ruby Interface

A DSL definition declares what arguments the #bind method accepts and how they map to CLI arguments.

Example:

Arguments.define do
  literal 'branch'
  value_option %i[set_upstream_to u], inline: true  # primary name with short alias :u
  operand :branch_name
end

When #bind is called, arguments are the values passed to it:

  • Arguments: Values passed to #bind
  • Positional arguments: Arguments identified by position
  • Keyword arguments: Arguments identified by name

Example:

args_def.bind('main', set_upstream_to: 'origin/main')
  • Positional argument: 'main'
  • Keyword argument: set_upstream_to: 'origin/main'

Calling Bound#to_a on the bound result produces the CLI argument array:

args_def.bind('main', set_upstream_to: 'origin/main').to_a
# => ['branch', '--set-upstream-to=origin/main', 'main']

Design

The class operates in two stages:

  1. Definition stage: DSL methods (#flag_option, #value_option, #operand, etc.) record argument definitions in internal data structures.

  2. Bind stage: #bind binds Ruby values and validates them against constraints, returning a Bound object.

The returned Bound object provides accessor methods for the bound values and handles the building phase, converting bound values to CLI arguments via Bound#to_a.

Key internal components:

  • +@ordered_definitions+: Array tracking all definitions in definition order
  • +@option_definitions+: Hash mapping option names to their definitions
  • +@operand_definitions+: Array of operand (positional argument) definitions
  • +@alias_map+: Maps option aliases to their primary names
  • +BUILDERS+: Hash of lambdas that convert values to CLI arguments by type
  • OperandAllocator: Handles Ruby-like operand allocation

Argument Ordering

Arguments are rendered in the exact order they are defined in the DSL block, regardless of type (options, operands, or static flags). This is important for git commands where argument order matters, such as when using -- to separate options from pathspecs.

Use #end_of_options to emit -- only when at least one following operand produces output, or #literal with '--' when -- must always be present.

Short Option Detection

Option names are automatically formatted using POSIX conventions:

  • Single-character names use single-dash prefix: :f-f
  • Multi-character names use double-dash prefix: :force--force

For inline values (inline: true), the separator also follows POSIX conventions:

  • Short options use no separator: -n3
  • Long options use = separator: --name=value

Negated flags always use double-dash format (e.g., -f--no-f when false).

The as: parameter can override this automatic detection when needed.

Option Types

The DSL supports several option types with modifiers:

Primary Option Types

  • #flag_option - Boolean flag (--flag when true, with negatable: true for --no-flag)
  • #value_option - Valued option (--flag value, with inline: true for --flag=value, or as_operand: true for operands)
  • #flag_or_value_option - Flag or value (--flag when true, --flag value when string, with inline: true and/or negatable: true modifiers)
  • #key_value_option - Key-value option that can be repeated (--trailer key=value)
  • #literal - Literal string always included in output
  • #custom_option - Custom option with builder block
  • #execution_option - Execution option (not included in CLI output, forwarded to command execution)

#value_option supports a repeatable: true parameter that allows the option to accept an array of values. This repeats the flag for each value (or outputs each as an operand when using as_operand: true):

Repeatable options:

value_option :config, repeatable: true
# config: ['a=b', 'c=d'] => ['--config', 'a=b', '--config', 'c=d']

value_option :sort, inline: true, repeatable: true
# sort: ['refname', '-committerdate'] => ['--sort=refname', '--sort=-committerdate']

end_of_options
value_option :pathspecs, as_operand: true, repeatable: true
# pathspecs: ['file1.txt', 'file2.txt'] => ['--', 'file1.txt', 'file2.txt']

Common Option Parameters

Most option types support parameters that affect input validation (checked during #bind):

  • required: - When true, the option key must be present in the provided opts. Raises ArgumentError if the key is missing. Defaults to false.

Supported by: #flag_option, #value_option, #flag_or_value_option, #key_value_option, #custom_option, #operand.

  • allow_nil: - When false (with required: true), the value cannot be nil. Raises ArgumentError if a nil value is provided. Defaults to true for options, false for operands.

Supported by: same as required:.

  • type: - Validates the value is an instance of the specified class(es). Accepts a single class or an array of classes. Raises ArgumentError if type doesn't match. This parameter only performs type checking during validation; the conversion of values to CLI argument strings is handled separately during the build phase — see the String Conversion section below. Defaults to nil (no validation).

Supported by: #flag_option, #value_option, #flag_or_value_option.

Note: #literal and #execution_option do not support these validation parameters.

These parameters affect output generation (what CLI arguments are produced):

  • as: - Override the CLI argument(s) derived from the option name Can be a String or an Array. Default is nil (derives from name).

  • allow_empty: - (#value_option only) When true, output the option even if the value is an empty string. Default is false (empty strings skipped).

  • repeatable: - (#value_option, #flag_or_value_option, and #operand only) Output an option or operand for each array element. Default is false.

  • skip_cli: - (#operand only) Bind, validate, and expose an operand accessor without emitting that operand in Bound#to_a. Default is false.

Operands (Positional Arguments)

Operands are mapped using Ruby-like semantics:

  1. Post-repeatable required operands are reserved first (from the end)
  2. Pre-repeatable operands are filled with remaining values (required first, then optional)
  3. Optional operands (with defaults) get values only if extras are available
  4. Repeatable operand gets whatever is left in the middle

This matches Ruby's parameter binding behavior, including patterns like def foo(a = default, *rest, b) where the required b is filled before optional a.

Nil Handling for Operands

When nil values are allowed (see required: and allow_nil: above), they have special output behavior:

  • For non-repeating operands: nil values consume an operand slot during binding but are omitted from the resulting command-line arguments array
  • For repeatable operands: nil values within the array raise an error

Option-like Operand Rejection

Operands that appear before a -- separator boundary in the argument definition are automatically validated to ensure their values don't start with -. This prevents user-supplied strings like '-s' from being misinterpreted as git flags when passed as positional arguments.

The -- boundary can come from:

  • A literal '--' definition
  • An end_of_options declaration

Operands after the -- boundary are not validated (they represent paths/filenames which may legitimately start with -). If no -- boundary exists in the definition, all operands are validated.

Options After Separator

Options that produce CLI flags (e.g. flag_option, value_option, key_value_option, custom_option) cannot be defined after a -- separator boundary. Git treats everything after -- as operands, so flags emitted there would be misinterpreted.

Only value_option with as_operand: true and execution_option are allowed after the boundary because they do not produce flag-prefixed output.

For example, this will raise +ArgumentError+ during definition:

Arguments.define do
  literal '--'
  flag_option :verbose
end #=> raises ArgumentError

Type Validation

The type: parameter provides declarative type validation for option values. When validation fails, an ArgumentError is raised with a descriptive message.

String Conversion

During the build phase, value-bearing option types (value_option, flag_or_value_option, key_value_option) and operand definitions convert their bound values to CLI argument strings by calling #to_s. This means any object with a meaningful #to_s implementation — Integer, Float, Pathname, etc. — can be passed as a value without the DSL needing to know about the type.

Note: flag_option values control presence or absence of a flag and are not stringified. custom_option builders receive the raw value and are responsible for producing CLI strings themselves.

The type: parameter does not affect this conversion; it only validates the Ruby class of the value before stringification.

Conflict Detection

Use #conflicts to declare mutually exclusive arguments. Names may refer to options (flag, value, flag-or-value, etc.) or operands (positional arguments) interchangeably. When #bind is called, if more than one argument in a conflict group is "present", an ArgumentError is raised.

An argument is considered present when its value is not nil, false, [], or ''.

Forbidden Value Combinations

#conflicts is presence-based — it cannot distinguish between semantically equivalent and contradictory combinations of negatable flags. Use #forbid_values to declare specific exact-value tuples that are invalid.

A forbid_values declaration matches only when every listed name has a bound value equal to the declared value (Ruby ==). Only matching tuples raise ArgumentError; all other value combinations are permitted. Names may be options or operands; aliases are canonicalized before comparison.

This is most useful for negatable flags where some value-pairings are contradictory but others are semantically equivalent and should remain valid.

The error message has the form:

"cannot specify :name1=value1 with :name2=value2"

At-Least-One Presence Validation

Use #requires_one_of to declare groups of arguments where at least one must be present. Names may refer to options (flag, value, flag-or-value, etc.) or operands (positional arguments) interchangeably. When #bind is called, if none of the arguments in a group is present, an ArgumentError is raised.

Conditional Argument Requirements

Use #requires and the when: form of #requires_one_of to declare that an argument (or at least one of a group) must be present only when a specific trigger argument is present. These constraints are evaluated during #bind: if the trigger is absent the check is skipped entirely.

An ArgumentError is raised at definition time if either the required name(s) or the trigger name are not known arguments, catching typos early.

Value Constraints

In addition to presence-based validation (#conflicts, #requires_one_of, and #requires) and value-combination constraints (#forbid_values), you can restrict the set of acceptable values for any value-type option using #allowed_values. If a bound value falls outside the configured set, #bind raises ArgumentError with a descriptive message.

This is typically used to model git options that accept only a fixed list of modes or strategies.

Examples:

Ordering example (end_of_options emits '--' only when path is present)

args_def = Arguments.define do
  operand :ref
  end_of_options
  operand :path
end
args_def.bind('HEAD', 'file.txt').to_a  # => ['HEAD', '--', 'file.txt']
args_def.bind('HEAD').to_a              # => ['HEAD']  # (no trailing --)

Short option detection

args_def = Arguments.define do
  flag_option :f                          # true → '-f'
  flag_option :force                      # true → '--force'
  value_option :n, inline: true           # 3 → '-n3'
  value_option :name, inline: true        # 'test' → '--name=test'
end

args_def.bind(f: true, force: true, n: 3, name: 'test').to_a
# => ['-f', '--force', '-n3', '--name=test']

Explicit override with as:

args_def = Arguments.define do
  flag_option :f, as: '--force'
end
args_def.bind(f: true).to_a  # => ['--force']

Required option with non-nil value

args_def = Arguments.define do
  value_option :upstream, inline: true, required: true, allow_nil: false
end
args_def.bind() #=> raise ArgumentError, "Required options not provided: :upstream"
args_def.bind(upstream: nil) #=> raise ArgumentError, "Required options cannot be nil: :upstream"
args_def.bind(upstream: 'origin').to_a  # => ['--upstream=origin']

Required option allowing nil (default)

args_def = Arguments.define do
  value_option :branch, inline: true, required: true
end
args_def.bind() #=> raise ArgumentError, "Required options not provided: :branch"
args_def.bind(branch: nil).to_a  # => []
args_def.bind(branch: 'main').to_a  # => ['--branch=main']

Simple operand (like git clone <repository>)

args_def = Arguments.define do
  literal 'clone'
  operand :repository, required: true
end
args_def.bind('https://github.com/user/repo').to_a
# => ['clone', 'https://github.com/user/repo']

Repeatable operand (like git add <paths>...)

args_def = Arguments.define do
  literal 'add'
  operand :paths, repeatable: true
end
args_def.bind('file1', 'file2', 'file3').to_a
# => ['add', 'file1', 'file2', 'file3']

git mv pattern (like git mv <sources>... <destination>)

args_def = Arguments.define do
  literal 'mv'
  operand :sources, repeatable: true, required: true
  operand :destination, required: true
end
args_def.bind('src1', 'src2', 'dest').to_a  # => ['mv', 'src1', 'src2', 'dest']

Nil value omitted from output

args = Arguments.define do
  operand :tree_ish, required: true, allow_nil: true
  operand :paths, repeatable: true
end.bind(nil, 'file1', 'file2')
args.to_a     # => ['file1', 'file2']
args.tree_ish # => nil
args.paths    # => ['file1', 'file2']

Operands before and after '--' end_of_options boundary

args_def = Arguments.define do
  operand :commit1
  operand :commit2
  end_of_options
  operand :paths, repeatable: true
end
args_def.bind('-s') #=> raise ArgumentError, "operand :commit1 value '-s' looks like a command-line option"
args_def.bind('HEAD', 'HEAD~1', '-file.txt').to_a
# => ['HEAD', 'HEAD~1', '--', '-file.txt']

All operands validated when no '--' boundary exists

args_def = Arguments.define do
  operand :path1, required: true
  operand :path2, required: true
end
args_def.bind('-s', 'file.txt')
#=> raise ArgumentError, "operand :path1 value '-s' looks like a command-line option"

Allowed: value_option as_operand after '--'

Arguments.define do
  literal '--'
  value_option :paths, as_operand: true, repeatable: true
end

Single type validation

args_def = Arguments.define do
  value_option :date, type: String, inline: true
end
args_def.bind(date: "2024-01-01").to_a  # => ['--date=2024-01-01']
args_def.bind(date: 12345) #=> raise ArgumentError, "The :date option must be a String, but was a Integer"

Multiple type validation (allows any of the specified types)

args_def = Arguments.define do
  value_option :timeout, type: [Integer, Float], inline: true
end
args_def.bind(timeout: 30).to_a    # => ['--timeout=30']
args_def.bind(timeout: 30.5).to_a  # => ['--timeout=30.5']
args_def.bind(timeout: "30")
#=> raise ArgumentError, "The :timeout option must be a Integer or Float, but was a String"

Numeric values are stringified automatically

args_def = Arguments.define do
  value_option :depth, inline: true
  value_option :jobs,  inline: true
end
args_def.bind(depth: 5, jobs: 4).to_a  # => ['--depth=5', '--jobs=4']

Pathname is also accepted (no type: needed)

args_def = Arguments.define do
  operand :path, required: true
end
args_def.bind(Pathname.new('/tmp/foo')).to_a  # => ['/tmp/foo']

Option vs option conflict

args_def = Arguments.define do
  flag_option :force
  flag_option :force_force
  conflicts :force, :force_force
end
args_def.bind(force: true, force_force: true) #=> raise ArgumentError, "cannot specify :force and :force_force"

Mixed option and operand conflict

args_def = Arguments.define do
  flag_option %i[merge m], as: '--merge'
  operand :tree_ish, required: true, allow_nil: true
  conflicts :merge, :tree_ish
end
args_def.bind('main', merge: true)  #=> raise ArgumentError, "cannot specify :merge and :tree_ish"
args_def.bind(nil, merge: true).to_a  # => ['--merge']

Reject contradictory pairs without blocking equivalent ones

args_def = Arguments.define do
  flag_option :all,            negatable: true
  flag_option :ignore_removal, negatable: true
  forbid_values all: true,    ignore_removal: true         # --all --ignore-removal: contradictory
  forbid_values no_all: true, no_ignore_removal: true     # --no-all --no-ignore-removal: contradictory
end
args_def.bind(all: true,    ignore_removal: true)
  #=> raise ArgumentError, 'cannot specify :all=true with :ignore_removal=true'
args_def.bind(all: true,    no_ignore_removal: true).to_a  # => ['--all', '--no-ignore-removal']
args_def.bind(no_all: true, ignore_removal: true).to_a     # => ['--no-all', '--ignore-removal']

Requiring at least one path source (options only)

args_def = Arguments.define do
  value_option :pathspec_from_file, inline: true
  end_of_options
  value_option :pathspec, as_operand: true, repeatable: true
  requires_one_of :pathspec, :pathspec_from_file
end
args_def.bind
  #=> raise ArgumentError, 'at least one of :pathspec, :pathspec_from_file must be provided'
args_def.bind(pathspec: ['file.txt']).to_a  # => ['--', 'file.txt']

Mixed option and operand group

args_def = Arguments.define do
  flag_option :all
  operand :paths, repeatable: true
  requires_one_of :all, :paths
end
args_def.bind
  #=> raise ArgumentError, 'at least one of :all, :paths must be provided'
args_def.bind('file.txt').to_a  # => ['file.txt']

Single conditional requirement

args_def = Arguments.define do
  flag_option :pathspec_file_nul
  value_option :pathspec_from_file, inline: true
  requires :pathspec_from_file, when: :pathspec_file_nul
end
args_def.bind(pathspec_file_nul: true, pathspec_from_file: 'paths.txt').to_a
# => ['--pathspec-file-nul', '--pathspec-from-file=paths.txt']
args_def.bind(pathspec_file_nul: true)
#=> raise ArgumentError, ':pathspec_file_nul requires :pathspec_from_file'
args_def.bind  # trigger absent — no error

Conditional at-least-one-of group

args_def = Arguments.define do
  flag_option :annotate
  value_option :message, inline: true
  value_option :file, inline: true
  requires_one_of :message, :file, when: :annotate
end
args_def.bind(annotate: true, message: 'v1.0').to_a  # => ['--annotate', '--message=v1.0']
args_def.bind(annotate: true)
#=> raise ArgumentError, ':annotate requires at least one of :message, :file'
args_def.bind  # trigger absent — no error

Restricting option values

args_def = Arguments.define do
  value_option :strategy, inline: true
  allowed_values :strategy, in: %w[ours theirs]
end
args_def.bind(strategy: 'ours').to_a      # => ['--strategy=ours']
args_def.bind(strategy: 'theirs').to_a # => ['--strategy=theirs']
args_def.bind(strategy: 'rebase')
# => raise ArgumentError, 'Invalid value for :strategy: expected one of ["ours", "theirs"], got "rebase"'

Defined Under Namespace

Classes: Bound

Constant Summary collapse

OPTION_TYPES_AFTER_SEPARATOR =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Option types allowed after a '--' separator boundary (they do not produce CLI flags)

%i[value_as_operand execution_option].freeze
VALUE_OPTION_TYPES_FOR_ALLOWED_VALUES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Option types that accept a string value — eligible for allowed_values constraints

%i[
  value inline_value value_as_operand
  flag_or_value flag_or_inline_value
].freeze
FLAG_OR_VALUE_OPTION_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

The subset of VALUE_OPTION_TYPES_FOR_ALLOWED_VALUES whose boolean values carry semantic meaning (true = emit flag, false = suppress flag) and must skip allowed_values validation rather than being compared against the set.

%i[
  flag_or_value flag_or_inline_value
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeArguments

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Arguments.



614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/git/commands/arguments.rb', line 614

def initialize
  @option_definitions = {}
  @alias_map = {} # Maps alias keys to primary keys
  @operand_definitions = []
  @conflicts = [] # Array of conflicting option pairs/groups
  @forbidden_values = [] # Array of forbidden exact-value tuples
  @requires_one_of = [] # Array of "at least one must be present" groups
  @ordered_definitions = [] # Tracks all definitions in definition order
  @past_separator = false # Tracks whether a '--' boundary has been defined
  @end_of_options_declared = false # Guards against duplicate end_of_options calls
  @negatable_companions = Set.new # Synthesized :no_<name> companion entries
end

Class Method Details

.define { ... } ⇒ Arguments

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Define a new Arguments instance using the DSL

Examples:

Basic flag

args_def = Arguments.define do
  flag_option :verbose
end
args_def.bind(verbose: true).to_a  # => ['--verbose']

Yields:

  • [] block evaluated in the context of the new Arguments instance via +instance_eval+, so DSL methods (#flag_option, #operand, etc.) are called directly without an explicit receiver

Returns:

  • (Arguments)

    The configured Arguments instance



608
609
610
611
612
# File 'lib/git/commands/arguments.rb', line 608

def self.define(&block)
  args = new
  args.instance_eval(&block) if block
  args
end

Instance Method Details

#allowed_values(name, in:)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Restrict a value option to a fixed set of accepted strings

Declares that the named option must only receive values from the given list when a value is provided. Validation runs during #bind, after type checking. nil and absent values are always skipped. Empty strings are skipped when allow_empty: true is set on the option. For repeatable: true options each element of the array is validated individually.

For {#flag_or_value_option} variants (including +negatable: true+), boolean values (+true+ / +false+) are skipped by this check because they control flag-emission behavior rather than representing candidate string values.

Examples:

Constrain chmod to '+x' or '-x'

args_def = Arguments.define do
  value_option :chmod, inline: true
  allowed_values :chmod, in: ['+x', '-x']
end
args_def.bind(chmod: '+x').to_a   # => ['--chmod=+x']
args_def.bind(chmod: 'rx')
  # => raise ArgumentError, 'Invalid value for :chmod: expected one of ["+x", "-x"], got "rx"'
args_def.bind.to_a              # => []  # (absent — no error)

Constrain cleanup to an enumerated set

args_def = Arguments.define do
  value_option :cleanup, inline: true
  allowed_values :cleanup, in: %w[verbatim whitespace strip]
end
args_def.bind(cleanup: 'verbatim').to_a  # => ['--cleanup=verbatim']
args_def.bind(cleanup: 'compact')
  # => raise ArgumentError, 'Invalid value for :cleanup: expected one of ["verbatim", "whitespace", "strip"], got "compact"'

Repeatable option — each element is validated

args_def = Arguments.define do
  value_option :strategy, inline: true, repeatable: true
  allowed_values :strategy, in: %w[ours theirs]
end
args_def.bind(strategy: %w[ours theirs]).to_a
  # => ['--strategy=ours', '--strategy=theirs']
args_def.bind(strategy: %w[ours other])
  # => raise ArgumentError, 'Invalid value for :strategy: expected one of ["ours", "theirs"], got "other"'

Parameters:

  • name (Symbol)

    the option name (primary or alias); must refer to a previously defined #value_option or #flag_or_value_option

  • in (#each)

    accepted values enumerable. Each value is coerced with +to_s+ and compared as a string.

Raises:

  • (ArgumentError)

    if +name+ is not a known option at definition time

  • (ArgumentError)

    if +name+ refers to a non-value option (e.g., a flag)

  • (ArgumentError)

    during #bind if the bound value is not in the accepted set, with a message of the form: "Invalid value for :name: expected one of [...], got \"actual\""



1638
1639
1640
1641
1642
# File 'lib/git/commands/arguments.rb', line 1638

def allowed_values(name, in:)
  sym = name.to_sym
  defn = validate_allowed_values_definition!(sym)
  defn[:allowed_values] = coerce_allowed_values_set!(sym, binding.local_variable_get(:in))
end

#bind(*positionals, **opts) ⇒ Bound

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Bind positionals and options, returning a Bound object with accessor methods

Unlike the internal build method which returns a raw Array, this method returns a Bound object that:

  • Provides accessor methods for all defined options and positional arguments
  • Automatically normalizes option aliases to their canonical names
  • Supports splatting via to_ary for seamless use with command(*bound)

Examples:

Simple splatting (same behavior as build)

def call(*, **)
  @execution_context.command_capturing(*ARGS.bind(*, **))
end

Inspecting options before command execution

args_def = Arguments.define do
  flag_option :force
  flag_option :remotes, as: ['-r', '--remotes']
  operand :branch_names, repeatable: true
end
bound_args = args_def.bind('branch1', 'branch2', force: true, remotes: true)
bound_args.force?         # => true
bound_args.remotes?       # => true
bound_args.branch_names   # => ['branch1', 'branch2']

Hash-style access for reserved names

args_def = Arguments.define do
  value_option :hash
end
bound_args = args_def.bind(hash: 'abc123')
bound_args[:hash]  # => 'abc123'

Parameters:

  • positionals (Array)

    positional argument values

  • opts (Hash)

    the keyword options

Returns:

  • (Bound)

    a frozen object with accessor methods for all arguments

Raises:

  • (ArgumentError)

    if unsupported options are provided or validation fails

  • (ArgumentError)

    if an operand value before a '--' boundary starts with '-'



1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
# File 'lib/git/commands/arguments.rb', line 1809

def bind(*positionals, **opts)
  normalized_opts = validate_and_normalize_options!(opts)
  allocated_positionals = allocate_and_validate_positionals(positionals)
  validate_bind_inputs!(normalized_opts, allocated_positionals)

  args_array = build_ordered_arguments(allocated_positionals, normalized_opts)
  options_hash = build_options_hash(normalized_opts)
  execution_option_names = option_names_by_type(:execution_option)
  flag_names = option_names_by_type(:flag)

  Bound.new(args_array, options_hash, allocated_positionals, execution_option_names, flag_names)
end

#conflicts(*names)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Declare that arguments conflict with each other (mutually exclusive)

Each call to #conflicts defines a separate group of mutually exclusive arguments. Names may refer to options (flag, value, flag-or-value, etc.) or operands (positional arguments). When #bind is called, if more than one argument in the same conflict group is "present", an ArgumentError is raised.

Presence semantics — an argument is present when its value is not nil, [], or ''. false is always treated as absent for all option types.

An ArgumentError is raised at definition time if any name given to +conflicts+ is not a known option or operand, catching typos early.

The error message has the general form:

"cannot specify :name1 and :name2"

Examples:

Option-only conflict group

args_def = Arguments.define do
  flag_option :gpg_sign
  flag_option :no_gpg_sign
  flag_option :force
  flag_option :no_force
  conflicts :gpg_sign, :no_gpg_sign
  conflicts :force, :no_force
end
args_def.bind(gpg_sign: true).to_a  # => ['--gpg-sign']

Mixed option and operand conflict

args_def = Arguments.define do
  flag_option %i[merge m], as: '--merge'
  operand :tree_ish, required: true, allow_nil: true
  end_of_options
  operand :paths, repeatable: true
  conflicts :merge, :tree_ish
end
args_def.bind(nil, 'file.txt', merge: true).to_a  # => ['--merge', '--', 'file.txt']
args_def.bind('main', 'file.txt', merge: true)
  # => raise ArgumentError, 'cannot specify :merge and :tree_ish'

Parameters:

  • names (Array<Symbol>)

    the option/operand names that conflict within this group

Raises:

  • (ArgumentError)

    if any name is not a known option or operand

  • (ArgumentError)

    if more than one argument in the same conflict group is present when building arguments



1273
1274
1275
1276
1277
1278
1279
1280
1281
# File 'lib/git/commands/arguments.rb', line 1273

def conflicts(*names)
  names.each do |name|
    sym = name.to_sym
    next if known_argument?(sym)

    raise ArgumentError, "unknown argument :#{sym} in conflicts declaration"
  end
  @conflicts << names.map(&:to_sym)
end

#custom_option(names, required: false, allow_nil: true) {|value| ... }

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define a custom option with a custom builder block

Examples:

Custom transformation (e.g., formatting a Date value)

args_def = Arguments.define do
  custom_option :since do |val|
    val ? "--since=#{val.strftime('%Y-%m-%d')}" : nil
  end
end
args_def.bind(since: Date.new(2024, 1, 1)).to_a  # => ['--since=2024-01-01']
args_def.bind.to_a                               # => []

Parameters:

  • names (Symbol, Array<Symbol>)

    the option name(s), first is primary

  • required (Boolean) (defaults to: false)

    whether the option must be provided (key must exist in opts)

  • allow_nil (Boolean) (defaults to: true)

    whether nil is allowed when required is true. Defaults to true. When false with required: true, raises ArgumentError if value is nil.

Yields:

  • (value)

    block that receives the option value and returns the CLI argument(s)

Yield Parameters:

  • value (Object)

    the bound value for this option

Yield Returns:

  • (String, Array<String>, nil)

    the CLI argument(s) to emit; nil or an empty array emits nothing

Raises:

  • (ArgumentError)

    if defined after an end_of_options or literal '--' boundary



1195
1196
1197
# File 'lib/git/commands/arguments.rb', line 1195

def custom_option(names, required: false, allow_nil: true, &block)
  register_option(names, type: :custom, builder: block, required: required, allow_nil: allow_nil)
end

#end_of_options(as: '--')

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Conditionally emit an options terminator only when at least one following argument produces output

This is the canonical form for declaring the options/operands boundary in a command definition. Unlike #literal with '--' which always emits the separator, end_of_options emits its terminator string only when at least one argument defined after it will be emitted as part of the CLI (for example operands or value_option ... as_operand: true). This avoids a trailing bare terminator when no pathspecs or other post-separator arguments are provided.

end_of_options also acts as an always-active validation boundary: operands defined before it are always validated for option-like values (starting with -), regardless of whether the terminator will ultimately be emitted.

Examples:

Basic usage (git checkout tree-ish -- pathspecs)

args_def = Arguments.define do
  flag_option :force
  operand :tree_ish, required: true, allow_nil: true
  end_of_options
  operand :pathspecs, repeatable: true
end
args_def.bind('HEAD', 'file.txt').to_a   # => ['HEAD', '--', 'file.txt']
args_def.bind('HEAD').to_a               # => ['HEAD'] # (no --, nothing after it)
args_def.bind(nil, 'file.txt').to_a      # => ['--', 'file.txt']
args_def.bind(nil).to_a                  # => []

Custom terminator (git rev-parse --end-of-options)

args_def = Arguments.define do
  flag_option :verify
  end_of_options as: '--end-of-options'
  operand :args, repeatable: true
end
args_def.bind('HEAD').to_a               # => ['--end-of-options', 'HEAD']
args_def.bind.to_a                       # => []

Parameters:

  • as (String) (defaults to: '--')

    the CLI token to emit as the options terminator (default '--'). Some commands use a different terminator; for example, git rev-parse uses '--end-of-options'.

Raises:

  • (ArgumentError)

    if called more than once per definition block

  • (ArgumentError)

    if a flag-producing option is defined after this call



1157
1158
1159
1160
1161
1162
1163
1164
# File 'lib/git/commands/arguments.rb', line 1157

def end_of_options(as: '--')
  raise ArgumentError, 'end_of_options cannot be declared twice' if @end_of_options_declared

  @ordered_definitions << { kind: :end_of_options }
  @end_of_options_declared = true
  @end_of_options_as = as
  @past_separator = true
end

#execution_option(names)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define an execution option (not included in CLI output, forwarded to command execution)

Execution options are omitted from the CLI argument array produced by Git::Commands::Arguments::Bound#to_a, but their values are still accessible on the Bound object. This is useful for options that control Ruby-side execution context (e.g., working directory) rather than git flags.

Examples:

Chdir option forwarded to execution context, not emitted as a CLI flag

args_def = Arguments.define do
  flag_option :verbose
  execution_option :chdir
end
bound = args_def.bind(verbose: true, chdir: '/tmp')
bound.to_a        # => ['--verbose']  # :chdir is not included
bound[:chdir]     # => '/tmp'          # still accessible on the Bound object

Parameters:

  • names (Symbol, Array<Symbol>)

    the option name(s), first is primary



1218
1219
1220
# File 'lib/git/commands/arguments.rb', line 1218

def execution_option(names)
  register_option(names, type: :execution_option)
end

#flag_option(names, as: nil, negatable: false, required: false, allow_nil: true, max_times: nil)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define a boolean flag option (--flag when true)

Examples:

Basic flag

args_def = Arguments.define do
  flag_option :force
end
args_def.bind(force: true).to_a   # => ['--force']
args_def.bind(force: false).to_a  # => []

Negatable flag (companion-key model)

args_def = Arguments.define do
  flag_option :full, negatable: true
end
args_def.bind(full: true).to_a      # => ['--full']
args_def.bind(no_full: true).to_a   # => ['--no-full']
args_def.bind(full: false).to_a     # => []

Negatable flag with required: true (either companion key satisfies the requirement)

args_def = Arguments.define do
  flag_option :verify, negatable: true, required: true
end
args_def.bind(verify: true).to_a    # => ['--verify']
args_def.bind(no_verify: true).to_a # => ['--no-verify']
args_def.bind(verify: false)
#=> raise ArgumentError, "at least one of :verify, :no_verify must be provided"
args_def.bind
#=> raise ArgumentError, "at least one of :verify, :no_verify must be provided"

Repeatable flag with max_times

args_def = Arguments.define do
  flag_option :force, max_times: 2
end
args_def.bind(force: true).to_a  # => ['--force']
args_def.bind(force: 1).to_a     # => ['--force']
args_def.bind(force: 2).to_a     # => ['--force', '--force']

Negatable flag with max_times

args_def = Arguments.define do
  flag_option :force, negatable: true, max_times: 2
end
args_def.bind(no_force: true).to_a  # => ['--no-force']
args_def.bind(force: 2).to_a        # => ['--force', '--force']

With required and allow_nil: false

args_def = Arguments.define do
  flag_option :force, required: true, allow_nil: false
end
args_def.bind() #=> raise ArgumentError, "Required options not provided: :force"
args_def.bind(force: nil) #=> raise ArgumentError, "Required options cannot be nil: :force"

Parameters:

  • names (Symbol, Array<Symbol>)

    the option name(s), first is primary

  • as (String, Array<String>, nil) (defaults to: nil)

    custom argument(s) to output (e.g., '-r' or ['--amend', '--no-edit'])

  • negatable (Boolean) (defaults to: false)

    when true, registers a companion no_<name> key that emits --no-<flag> when set to true. Both keys use standard boolean semantics: true emits the flag, false or absent emits nothing. A conflict is automatically registered between the two keys so that name: true, no_name: true raises at bind time. The primary key must be snake_case (e.g. :verify, :three_way). When as: is given, it must be a long-form (--flag) String; Arrays and short-form flags (e.g. -S) are not compatible with negatable: true because the synthesized companion is always --no-<flag>.

  • required (Boolean) (defaults to: false)

    whether the option must be provided (the key must be present in opts). When combined with +negatable: true+, a requires_one_of [name, no_name] group is automatically registered so that either the primary or companion key satisfies the requirement (e.g. bind(no_verify: true) satisfies required: true for :verify). Note that under the companion-key model, bind(verify: false) does not satisfy the requirement because false is treated as absent.

  • allow_nil (Boolean) (defaults to: true)

    whether nil is allowed when required is true. Defaults to true. When false with required: true, raises ArgumentError if value is nil. Cannot be combined with +negatable: true+ and +required: true+ — raises ArgumentError at definition time (nil is already caught by the auto +requires_one_of+ group).

  • max_times (Integer, nil) (defaults to: nil)

    maximum number of times the flag may be repeated (default: nil). When set, the caller may pass a positive Integer up to this limit to emit the flag multiple times (e.g. force: 2 emits --force --force). Must be an Integer >= 2; 0 and 1 raise ArgumentError at definition time. When nil (the default), only boolean values are accepted.

Raises:

  • (ArgumentError)

    if defined after an end_of_options or literal '--' boundary

  • (ArgumentError)

    if max_times is not nil and not an Integer >= 2

  • (ArgumentError)

    if negatable: true and the primary key is not snake_case

  • (ArgumentError)

    if negatable: true and the generated no_<name> key collides with an already-registered key

  • (ArgumentError)

    if negatable: true and as: is an Array

  • (ArgumentError)

    if negatable: true and as: is not a long-form (--flag) String

  • (ArgumentError)

    if negatable: true and required: true and allow_nil: false



725
726
727
728
729
730
731
732
733
734
735
736
# File 'lib/git/commands/arguments.rb', line 725

def flag_option(names, as: nil, negatable: false, required: false, allow_nil: true, max_times: nil)
  primary = Array(names).first
  validate_max_times!(primary, max_times)

  if negatable
    register_negatable_flag_pair(names, as: as, required: required,
                                        allow_nil: allow_nil, max_times: max_times)
  else
    register_option(names, type: :flag, as: as, expected_type: nil, validator: nil,
                           required: required, allow_nil: allow_nil, max_times: max_times)
  end
end

#flag_or_value_option(names, as: nil, type: nil, negatable: false, inline: false, repeatable: false, required: false, allow_nil: true)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define a flag or value option

This is a flexible option type that outputs:

  • Just the flag (--flag) when value is true
  • Nothing when value is false
  • Flag with value when value is any non-boolean, non-nil object (stringified via #to_s; e.g., --flag value or --flag=value if inline: true)
  • Nothing when value is nil

Examples:

Basic flag or value (new capability - not possible with old DSL)

args_def = Arguments.define do
  flag_or_value_option :contains
end
args_def.bind(contains: true).to_a       # => ['--contains']
args_def.bind(contains: false).to_a      # => []
args_def.bind(contains: "abc123").to_a   # => ['--contains', 'abc123']
args_def.bind(contains: nil).to_a        # => []

With inline: true

args_def = Arguments.define do
  flag_or_value_option :gpg_sign, inline: true
end
args_def.bind(gpg_sign: true).to_a    # => ['--gpg-sign']
args_def.bind(gpg_sign: false).to_a   # => []
args_def.bind(gpg_sign: "KEY").to_a   # => ['--gpg-sign=KEY']
args_def.bind(gpg_sign: nil).to_a     # => []

With negatable: true (companion-key model)

args_def = Arguments.define do
  flag_or_value_option :verify, negatable: true
end
args_def.bind(verify: true).to_a       # => ['--verify']
args_def.bind(verify: false).to_a      # => []
args_def.bind(no_verify: true).to_a    # => ['--no-verify']
args_def.bind(verify: "KEYID").to_a    # => ['--verify', 'KEYID']
args_def.bind(verify: nil).to_a        # => []

With negatable: true and inline: true

args_def = Arguments.define do
  flag_or_value_option :sign, negatable: true, inline: true
end
args_def.bind(sign: true).to_a      # => ['--sign']
args_def.bind(sign: false).to_a     # => []
args_def.bind(no_sign: true).to_a   # => ['--no-sign']
args_def.bind(sign: "KEY").to_a     # => ['--sign=KEY']
args_def.bind(sign: nil).to_a       # => []

With inline: true and repeatable: true

args_def = Arguments.define do
  flag_or_value_option :recurse_submodules, inline: true, repeatable: true
end
args_def.bind(recurse_submodules: true).to_a       # => ['--recurse-submodules']
args_def.bind(recurse_submodules: 'lib/').to_a     # => ['--recurse-submodules=lib/']
args_def.bind(recurse_submodules: ['lib/', 'ext/']).to_a
# => ['--recurse-submodules=lib/', '--recurse-submodules=ext/']
args_def.bind(recurse_submodules: [nil])
# => raise_error ArgumentError, /Invalid value for flag_or_inline_value/

Parameters:

  • names (Symbol, Array<Symbol>)

    the option name(s), first is primary

  • as (String, nil) (defaults to: nil)

    custom option string

  • type (Class, Array<Class>, nil) (defaults to: nil)

    expected type(s) for validation

  • negatable (Boolean) (defaults to: false)

    when true, registers a companion no_<name> key that emits --no-<flag> when set to true. The positive key retains flag-or-value semantics; the negative key is boolean-only (accepts only true/false/nil). A conflict is automatically registered so that name: true, no_name: true raises at bind time. The primary key must be snake_case. When as: is given, it must be a long-form (--flag) String; Arrays and short-form flags (e.g. -S) are not compatible with negatable: true because the synthesized companion is always --no-<flag>.

  • inline (Boolean) (defaults to: false)

    when true, outputs --flag=value instead of --flag value (default: false)

  • repeatable (Boolean) (defaults to: false)

    when true, accepts an Array of values and repeats the option for each element. Each element must be +true+, +false+, or a non-nil object (which is stringified via +#to_s+); nil elements raise ArgumentError at bind time. A single (non-Array) value is also accepted. Default false.

  • required (Boolean) (defaults to: false)

    whether the option must be provided (the key must be present in opts). When combined with +negatable: true+, a requires_one_of [name, no_name] group is automatically registered so that either side satisfies the requirement. Note that bind(name: false) does not satisfy the requirement because false is treated as absent under the companion-key model.

  • allow_nil (Boolean) (defaults to: true)

    whether nil is allowed when required is true. Defaults to true. Cannot be combined with +negatable: true+ and +required: true+ — raises ArgumentError at definition time (nil is already caught by the auto +requires_one_of+ group).

Raises:

  • (ArgumentError)

    at bind time if +repeatable: true+ is used and any Array element is nil

  • (ArgumentError)

    if defined after an end_of_options or literal '--' boundary

  • (ArgumentError)

    if negatable: true and the primary key is not snake_case

  • (ArgumentError)

    if negatable: true and the generated no_<name> key collides with an already-registered key

  • (ArgumentError)

    if negatable: true and as: is an Array

  • (ArgumentError)

    if negatable: true and as: is not a long-form (--flag) String

  • (ArgumentError)

    if negatable: true and required: true and allow_nil: false



980
981
982
983
984
985
986
987
988
989
990
991
# File 'lib/git/commands/arguments.rb', line 980

def flag_or_value_option(names, as: nil, type: nil, negatable: false, inline: false,
                         repeatable: false, required: false, allow_nil: true)
  if negatable
    register_negatable_flag_or_value_pair(names, as: as, type: type, inline: inline,
                                                 repeatable: repeatable, required: required,
                                                 allow_nil: allow_nil)
  else
    option_type = inline ? :flag_or_inline_value : :flag_or_value
    register_option(names, type: option_type, as: as, expected_type: type,
                           repeatable: repeatable, required: required, allow_nil: allow_nil)
  end
end

#forbid_values(**pairs)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Declare that an exact combination of argument values is forbidden

Each call to #forbid_values defines one forbidden tuple. A tuple matches when every listed name is present (has a bound value after alias normalization) and each value equals the declared value exactly (Ruby ==). When a tuple matches, #bind raises ArgumentError.

This fills the gap left by #conflicts, which only checks presence. forbid_values is useful for negatable flags whose combinations can be semantically equivalent or contradictory depending on the actual boolean values — presence-based exclusion would be too coarse.

Names may refer to options (flag, value, flag-or-value, etc.) or operands (positional arguments). Alias names are accepted and canonicalized to their primary names.

An ArgumentError is raised at definition time if any name is not a known option or operand.

The error message has the form:

"cannot specify :name1=value1 with :name2=value2"

Examples:

Reject only the contradictory negatable flag combinations

args_def = Arguments.define do
  flag_option :all,            negatable: true
  flag_option :ignore_removal, negatable: true
  # --all --ignore-removal: contradictory (add ALL vs ignore removals)
  forbid_values all: true,    ignore_removal: true
  # --no-all --no-ignore-removal: contradictory (ignore removals vs include removals)
  forbid_values no_all: true, no_ignore_removal: true
end
# Contradictory tuples raise:
args_def.bind(all: true, ignore_removal: true)
  # => raise ArgumentError, 'cannot specify :all=true with :ignore_removal=true'
args_def.bind(no_all: true, no_ignore_removal: true)
  # => raise ArgumentError, 'cannot specify :no_all=true with :no_ignore_removal=true'
# Semantically compatible pairs are allowed:
args_def.bind(all: true,    no_ignore_removal: true).to_a  # => ['--all', '--no-ignore-removal']
args_def.bind(no_all: true, ignore_removal: true).to_a     # => ['--no-all', '--ignore-removal']

Parameters:

  • pairs (Hash)

    keyword pairs mapping argument name to forbidden value

Raises:

  • (ArgumentError)

    if any name in +pairs+ is not a known option or operand

  • (ArgumentError)

    during #bind if all names are present and all values exactly match the declared tuple



1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
# File 'lib/git/commands/arguments.rb', line 1334

def forbid_values(**pairs)
  raise ArgumentError, 'forbid_values must be given at least one name-value pair' if pairs.empty?

  pairs.each_key do |name|
    sym = name.to_sym
    next if known_argument?(sym)

    raise ArgumentError, "unknown argument :#{sym} in forbid_values declaration"
  end
  canonical = pairs.transform_keys { |k| @alias_map[k] || k }
  @forbidden_values << canonical
end

#key_value_option(names, as: nil, key_separator: '=', inline: false, required: false, allow_nil: true)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define a key-value option that can be specified multiple times

This is useful for git options like --trailer that take key=value pairs and can be repeated. Accepts Hash or Array of arrays for flexible input.

Examples:

Basic key-value (like --trailer)

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer'
end
args_def.bind(trailers: { 'Signed-off-by' => 'John' }).to_a
# => ['--trailer', 'Signed-off-by=John']

Hash with array values (multiple values for same key)

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer'
end
args_def.bind(trailers: { 'Signed-off-by' => ['John', 'Jane'] }).to_a
# => ['--trailer', 'Signed-off-by=John', '--trailer', 'Signed-off-by=Jane']

Array of arrays (full ordering control)

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer'
end
args_def.bind(trailers: [['Signed-off-by', 'John'], ['Acked-by', 'Bob']]).to_a
# => ['--trailer', 'Signed-off-by=John', '--trailer', 'Acked-by=Bob']

Key without value (nil value omits separator)

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer'
end
args_def.bind(trailers: [['Acked-by', nil]]).to_a
# => ['--trailer', 'Acked-by']

Nil in array values produces key-only entries

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer'
end
args_def.bind(trailers: { 'Key' => ['Value1', nil, 'Value2'] }).to_a
# => ['--trailer', 'Key=Value1', '--trailer', 'Key', '--trailer', 'Key=Value2']

With custom separator

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer', key_separator: ': '
end
args_def.bind(trailers: { 'Signed-off-by' => 'John' }).to_a
# => ['--trailer', 'Signed-off-by: John']

Empty values produce no output

args_def = Arguments.define do
  key_value_option :trailers, as: '--trailer', required: true
end
args_def.bind(trailers: {}).to_a   # => []
args_def.bind(trailers: []).to_a   # => []
args_def.bind(trailers: nil).to_a  # => []

Parameters:

  • names (Symbol, Array<Symbol>)

    the option name(s), first is primary

  • as (String, nil) (defaults to: nil)

    custom option string (e.g., '--trailer')

  • key_separator (String) (defaults to: '=')

    separator between key and value (default: '=')

  • inline (Boolean) (defaults to: false)

    when true, outputs --flag=key=value instead of --flag key=value

  • required (Boolean) (defaults to: false)

    whether the option must be provided (key must exist in opts). Note: empty hash/array is considered "present" and produces no output without error.

  • allow_nil (Boolean) (defaults to: true)

    whether nil is allowed when required is true

Raises:

  • (ArgumentError)

    at bind time if array input is not a [key, value] pair or array of pairs

  • (ArgumentError)

    at bind time if a sub-array has more than 2 elements

  • (ArgumentError)

    at bind time if a key is nil, empty, or contains the separator

  • (ArgumentError)

    at bind time if a value is a Hash or Array (non-scalar)

  • (ArgumentError)

    if defined after an end_of_options or literal '--' boundary



1073
1074
1075
1076
1077
# File 'lib/git/commands/arguments.rb', line 1073

def key_value_option(names, as: nil, key_separator: '=', inline: false, required: false, allow_nil: true)
  option_type = inline ? :inline_key_value : :key_value
  register_option(names, type: option_type, as: as, key_separator: key_separator,
                         required: required, allow_nil: allow_nil)
end

#literal(flag_string)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define a literal string that is always included in the output

Literals are output at their definition position (not grouped at the start). This allows precise control over argument ordering, which is important for git commands where argument position matters.

Examples:

Static flag for subcommand mode

args_def = Arguments.define do
  literal '--delete'
  flag_option :force
  operand :branches, repeatable: true
end
args_def.bind('feature', force: true).to_a  # => ['--delete', '--force', 'feature']

Static separator between options and pathspecs

args_def = Arguments.define do
  flag_option :force
  operand :tree_ish
  literal '--'
  operand :paths, repeatable: true
end
args_def.bind('HEAD', 'file.txt', force: true).to_a
# => ['--force', 'HEAD', '--', 'file.txt']

Parameters:

  • flag_string (String)

    the static flag string (e.g., '--', '--no-progress')



1107
1108
1109
1110
# File 'lib/git/commands/arguments.rb', line 1107

def literal(flag_string)
  @ordered_definitions << { kind: :static, flag: flag_string }
  @past_separator = true if flag_string == '--'
end

#operand(name, required: false, repeatable: false, default: nil, allow_nil: false, skip_cli: false)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define an operand (positional argument in Ruby terminology)

Operands are mapped to values following Ruby method signature semantics. Required operands before a repeatable are filled left-to-right, required operands after a repeatable are filled from the end, and the repeatable gets whatever remains in the middle.

Examples:

Required operand (like def clone(repository))

args_def = Arguments.define do
  operand :repository, required: true
end
args_def.bind('https://github.com/user/repo').to_a
# => ['https://github.com/user/repo']

Optional operand with default (like def log(commit = 'HEAD'))

args_def = Arguments.define do
  operand :commit, default: 'HEAD'
end
args_def.bind().to_a        # => ['HEAD']
args_def.bind('main').to_a  # => ['main']

Repeatable operand (like def add(*paths))

args_def = Arguments.define do
  operand :paths, repeatable: true
end
args_def.bind('file1', 'file2', 'file3').to_a
# => ['file1', 'file2', 'file3']

Required repeatable with at least one value (like def rm(*paths) with validation)

args_def = Arguments.define do
  operand :paths, repeatable: true, required: true
end
args_def.bind() #=> raise ArgumentError, "at least one value is required for paths"
args_def.bind('file1').to_a  # => ['file1']

git mv pattern (like def mv(*sources, destination))

args_def = Arguments.define do
  operand :sources, repeatable: true, required: true
  operand :destination, required: true
end
args_def.bind('src1', 'src2', 'dest').to_a  # => ['src1', 'src2', 'dest']
args_def.bind('src', 'dest').to_a           # => ['src', 'dest']

Optional before variadic with required after (like def foo(a = 'default', *middle, b))

args_def = Arguments.define do
  operand :a, default: 'default_a'
  operand :middle, repeatable: true
  operand :b, required: true
end
args_def.bind('x').to_a           # => ['default_a', 'x']
args_def.bind('x', 'y').to_a      # => ['x', 'y']
args_def.bind('x', 'm', 'y').to_a # => ['x', 'm', 'y']

Operand after end_of_options boundary (pathspec after --)

args_def = Arguments.define do
  flag_option :force
  end_of_options
  operand :paths, repeatable: true
end
args_def.bind('file1', 'file2', force: true).to_a
# => ['--force', '--', 'file1', 'file2']

Complex pattern (like def diff(commit1, commit2 = nil, *paths))

args_def = Arguments.define do
  operand :commit1, required: true
  operand :commit2
  end_of_options
  operand :paths, repeatable: true
end
args_def.bind('HEAD~1').to_a  # => ['HEAD~1']
args_def.bind('HEAD~1', 'HEAD').to_a  # => ['HEAD~1', 'HEAD']
args_def.bind('HEAD~1', 'HEAD', 'file.rb').to_a
# => ['HEAD~1', 'HEAD', '--', 'file.rb']

Required operand that allows nil (like git checkout [tree-ish] -- paths)

args_def = Arguments.define do
  operand :tree_ish, required: true, allow_nil: true
  end_of_options
  operand :paths, repeatable: true
end
args_def.bind(nil, 'file1.txt', 'file2.txt').to_a
# => ['--', 'file1.txt', 'file2.txt']
args_def.bind('HEAD', 'file.rb').to_a
# => ['HEAD', '--', 'file.rb']
args_def.bind(nil, 'file.rb').to_a
# => ['--', 'file.rb']

Parameters:

  • name (Symbol)

    the operand name (used in error messages)

  • required (Boolean) (defaults to: false)

    whether the argument is required. For repeatable operands, this means at least one value must be provided.

  • repeatable (Boolean) (defaults to: false)

    whether the argument accepts multiple values (like Ruby's splat operator *args). Only one repeatable operand is allowed per definition; attempting to define a second will raise an ArgumentError.

  • default (Object) (defaults to: nil)

    the default value if not provided. For repeatable operands, this should be an array (e.g., default: ['.']).

  • allow_nil (Boolean) (defaults to: false)

    whether nil is a valid value for a required operand. When true, nil consumes the operand slot but is omitted from output. This is useful for commands like git checkout where the tree-ish is required to consume a slot but may be nil to restore from the index. Defaults to false.

  • skip_cli (Boolean) (defaults to: false)

    whether this operand participates in binding, validation, and accessors but is omitted from CLI argv emission. Defaults to false.

Raises:

  • (ArgumentError)

    during #bind if the operand appears before a '--' boundary (or no boundary exists) and the bound value starts with '-'



1762
1763
1764
1765
1766
# File 'lib/git/commands/arguments.rb', line 1762

def operand(name, required: false, repeatable: false, default: nil, allow_nil: false,
            skip_cli: false)
  validate_single_repeatable!(name) if repeatable
  add_operand_definition(name, required, repeatable, default, allow_nil, skip_cli)
end

#requires(name, **kwargs)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Declare that name must be present whenever the trigger argument when: is present

When #bind is called, if the trigger argument is present and name is absent, an ArgumentError is raised. If the trigger is absent, the check is skipped.

Presence semantics — two slightly different rules apply:

  • when: trigger — the trigger is considered present when its value is not nil, false, [], or ''. A value of false is treated as absent. If you need an explicit negative form for a negatable flag, use its no_<name> companion key instead.
  • Required argumentname is considered present when its value is not nil, false, [], or ''.

An ArgumentError is raised at definition time if either name or the when: trigger is not a known option or operand, catching typos early.

The error message has the form:

":trigger requires :name"

Examples:

Require pathspec_from_file when pathspec_file_nul is present

args_def = Arguments.define do
  flag_option :pathspec_file_nul
  value_option :pathspec_from_file, inline: true
  requires :pathspec_from_file, when: :pathspec_file_nul
end
args_def.bind(pathspec_file_nul: true, pathspec_from_file: 'paths.txt').to_a
# => ['--pathspec-file-nul', '--pathspec-from-file=paths.txt']
args_def.bind(pathspec_file_nul: true)
# => raise ArgumentError, ':pathspec_file_nul requires :pathspec_from_file'
args_def.bind  # trigger absent — no error

Require dry_run when ignore_missing is present

args_def = Arguments.define do
  flag_option :dry_run
  flag_option :ignore_missing
  requires :dry_run, when: :ignore_missing
end
args_def.bind(ignore_missing: true)
# => raise ArgumentError, ':ignore_missing requires :dry_run'

Parameters:

  • name (Symbol)

    the option/operand name that must be present

  • kwargs (Hash)

    a customizable set of options

Options Hash (**kwargs):

  • :when (Symbol)

    the trigger argument; when present, name must also be present

Raises:

  • (ArgumentError)

    if when: is not provided

  • (ArgumentError)

    if name or the when: trigger is not a known option or operand

  • (ArgumentError)

    if the trigger is present and name is absent when binding arguments



1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
# File 'lib/git/commands/arguments.rb', line 1567

def requires(name, **kwargs)
  condition = kwargs.delete(:when)
  raise ArgumentError, 'requires: `when:` keyword is required' unless condition
  raise ArgumentError, "requires: unknown keyword arguments: #{kwargs.keys.inspect}" unless kwargs.empty?

  sym = name.to_sym
  validate_requires_name!(sym)
  canonical_trigger = resolve_requires_condition(condition)
  @requires_one_of << { names: [@alias_map[sym] || sym], condition: canonical_trigger, single: true }
end

#requires_exactly_one_of(*names)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Declare that exactly one of the named arguments must be present when binding

This is a convenience composite that combines #requires_one_of (at least one must be present) and #conflicts (at most one may be present). Use it when a group of arguments is mutually exclusive and the caller must supply precisely one of them.

The call:

requires_exactly_one_of :a, :b, :c

is exactly equivalent to:

requires_one_of :a, :b, :c conflicts :a, :b, :c

Presence semantics — inherits the rules from the constituent methods. See #requires_one_of and #conflicts for the full details.

An ArgumentError is raised at definition time if any name is not a known option or operand, catching typos early.

Error messages reuse the formats from the constituent methods:

"at least one of :a, :b, :c must be provided" # zero present "cannot specify :a and :b" # two or more present

Examples:

Mode flags where exactly one must be supplied

args_def = Arguments.define do
  flag_option :mode_a
  flag_option :mode_b
  flag_option :mode_c
  requires_exactly_one_of :mode_a, :mode_b, :mode_c
end
args_def.bind(mode_a: true).to_a  # => ['--mode-a']
args_def.bind
  # => raise ArgumentError, 'at least one of :mode_a, :mode_b, :mode_c must be provided'
args_def.bind(mode_a: true, mode_c: true)
  # => raise ArgumentError, 'cannot specify :mode_a and :mode_c'

Parameters:

  • names (Array<Symbol>)

    the option/operand names where exactly one must be present

Raises:

  • (ArgumentError)

    if any name is not a known option or operand

  • (ArgumentError)

    at bind time if none of the arguments in the group is present

  • (ArgumentError)

    at bind time if more than one argument in the group is present



1506
1507
1508
1509
# File 'lib/git/commands/arguments.rb', line 1506

def requires_exactly_one_of(*names)
  requires_one_of(*names)
  conflicts(*names)
end

#requires_one_of(*names, **kwargs)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Declare that at least one of the named arguments must be present when binding

Each call to #requires_one_of defines an independent "at least one" group. When #bind is called, if none of the arguments in the group is present, an ArgumentError is raised.

Conditional form — when when: is given, the check is only performed if the named trigger argument is present. If the trigger is absent the group is skipped entirely.

Presence semantics — two slightly different rules apply:

  • when: trigger — the trigger is considered present when its value is not nil, false, [], or ''. A flag set to false means absent, so the trigger does not fire.
  • Satisfied-by check — a group member is considered present when its value is not nil, false, [], or ''. false is treated as absent for all option types under the companion-key model.

Names may refer to options (flag, value, flag-or-value, etc.) or operands (positional arguments) interchangeably. Alias resolution happens before the check, so supplying an alias for one of the named options counts as that option being present.

An ArgumentError is raised at definition time if any name (including the when: trigger) is not a known option or operand, catching typos early.

The error message has the general form (unconditional):

"at least one of :name1, :name2 must be provided"

The error message has the general form (conditional, when: given):

":trigger requires at least one of :name1, :name2"

Examples:

At-least-one of two keyword options (unconditional)

args_def = Arguments.define do
  value_option :pathspec_from_file, inline: true
  end_of_options
  value_option :pathspec, as_operand: true, repeatable: true
  requires_one_of :pathspec, :pathspec_from_file
end
args_def.bind(pathspec: ['file.txt']).to_a  # => ['--', 'file.txt']
args_def.bind(pathspec_from_file: 'paths.txt').to_a
# => ['--pathspec-from-file=paths.txt']
args_def.bind
  # => raise ArgumentError, 'at least one of :pathspec, :pathspec_from_file must be provided'

Mixed option and operand group (unconditional)

args_def = Arguments.define do
  flag_option :all
  operand :paths, repeatable: true
  requires_one_of :all, :paths
end
args_def.bind('file.txt').to_a     # passes — :paths is present
args_def.bind(all: true).to_a      # passes — :all is present
args_def.bind
  # => raise ArgumentError, 'at least one of :all, :paths must be provided'

Multiple independent groups (unconditional)

args_def = Arguments.define do
  flag_option :commit
  flag_option :all
  value_option :pathspec_from_file, inline: true
  end_of_options
  value_option :pathspec, as_operand: true, repeatable: true
  requires_one_of :commit, :all
  requires_one_of :pathspec, :pathspec_from_file
end

Conditional at-least-one-of group (when: form)

args_def = Arguments.define do
  flag_option :annotate
  value_option :message, inline: true
  value_option :file, inline: true
  requires_one_of :message, :file, when: :annotate
end
args_def.bind(annotate: true, message: 'v1.0').to_a  # passes
args_def.bind(annotate: true)
  # => raise ArgumentError, ':annotate requires at least one of :message, :file'
args_def.bind  # trigger absent — no error

Parameters:

  • names (Array<Symbol>)

    the option/operand names where at least one must be present

  • kwargs (Hash)

    a customizable set of options

Options Hash (**kwargs):

  • :when (Symbol)

    optional trigger argument; when given, the check is only performed if the trigger argument is present

Raises:

  • (ArgumentError)

    if no names are given

  • (ArgumentError)

    if any name (or the when: trigger) is not a known option or operand

  • (ArgumentError)

    if none of the arguments in the group is present when binding arguments (and the trigger, if any, is present)



1445
1446
1447
1448
1449
1450
1451
1452
1453
# File 'lib/git/commands/arguments.rb', line 1445

def requires_one_of(*names, **kwargs)
  condition = kwargs.delete(:when)
  raise ArgumentError, "requires_one_of: unknown keyword arguments: #{kwargs.keys.inspect}" unless kwargs.empty?
  raise ArgumentError, 'requires_one_of must be given at least one argument name' if names.empty?

  canonical_group = canonicalize_requires_names(names)
  canonical_condition = resolve_requires_condition(condition)
  @requires_one_of << { names: canonical_group, condition: canonical_condition, single: false }
end

#value_option(names, as: nil, type: nil, inline: false, as_operand: false, allow_empty: false, repeatable: false, required: false, allow_nil: true)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Define a valued option (--flag value as separate arguments)

This option type supports three output modes controlled by inline: and as_operand::

  • Default: --flag value (flag and value as separate arguments)
  • Inline: --flag=value (single argument with inline: true)
  • Operand: value (no flag, just the value with as_operand: true)

Examples:

Basic value (default mode)

args_def = Arguments.define do
  value_option :branch
end
args_def.bind(branch: 'main').to_a  # => ['--branch', 'main']

Inline value

args_def = Arguments.define do
  value_option :format, inline: true
end
args_def.bind(format: 'short').to_a  # => ['--format=short']

Operand value (no flag output)

args_def = Arguments.define do
  value_option :ref, as_operand: true
end
args_def.bind(ref: 'HEAD').to_a  # => ['HEAD']

Operand with end_of_options boundary

args_def = Arguments.define do
  end_of_options
  value_option :paths, as_operand: true
end
args_def.bind(paths: 'file.txt').to_a  # => ['--', 'file.txt']

Multi-valued (default mode) - repeats option for each value

args_def = Arguments.define do
  value_option :config, repeatable: true
end
args_def.bind(config: 'a=b').to_a  # => ['--config', 'a=b']
args_def.bind(config: ['a=b', 'c=d']).to_a  # => ['--config', 'a=b', '--config', 'c=d']
args_def.bind(config: nil).to_a  # => []

Multi-valued with inline - repeats inline option for each value

args_def = Arguments.define do
  value_option :sort, inline: true, repeatable: true
end
args_def.bind(sort: ['refname', '-committerdate']).to_a
# => ['--sort=refname', '--sort=-committerdate']

Multi-valued with operand - outputs values without flags

args_def = Arguments.define do
  end_of_options
  value_option :pathspecs, as_operand: true, repeatable: true
end
args_def.bind(pathspecs: ['file1.txt', 'file2.txt']).to_a
# => ['--', 'file1.txt', 'file2.txt']

With type validation

args_def = Arguments.define do
  value_option :branch, type: String
end
args_def.bind(branch: 'main').to_a  # => ['--branch', 'main']

With allow_empty

args_def = Arguments.define do
  value_option :message, allow_empty: true
end
args_def.bind(message: "").to_a     # => ['--message', '']
args_def.bind(message: "text").to_a  # => ['--message', 'text']

args_def2 = Arguments.define do
  value_option :message  # allow_empty defaults to false
end
args_def2.bind(message: "").to_a     # => []
args_def2.bind(message: "text").to_a  # => ['--message', 'text']

With required

args_def = Arguments.define do
  value_option :message, required: true
end
args_def.bind(message: 'text').to_a  # => ['--message', 'text']
args_def.bind(message: nil).to_a  # => []
args_def.bind() #=> raise ArgumentError, "Required options not provided: :message"

With required and allow_nil: false

args_def = Arguments.define do
  value_option :message, required: true, allow_nil: false
end
args_def.bind(message: 'text').to_a  # => ['--message', 'text']
args_def.bind(message: nil) #=> raise ArgumentError, "Required options cannot be nil: :message"
args_def.bind() #=> raise ArgumentError, "Required options not provided: :message"

Parameters:

  • names (Symbol, Array<Symbol>)

    the option name(s), first is primary

  • as (String, nil) (defaults to: nil)

    custom option string (arrays not supported for value types)

  • type (Class, Array<Class>, nil) (defaults to: nil)

    expected type(s) for validation. Raises ArgumentError with descriptive message if value doesn't match.

  • inline (Boolean) (defaults to: false)

    when true, outputs --flag=value as single argument instead of --flag value as separate arguments (default: false). Cannot be combined with as_operand:.

  • as_operand (Boolean) (defaults to: false)

    when true, outputs value as operand without flag (default: false). Cannot be combined with inline:.

  • allow_empty (Boolean) (defaults to: false)

    whether to include the option even when value is an empty string. When false (default), empty strings are skipped entirely. When true, the option and empty value are included in the output.

  • repeatable (Boolean) (defaults to: false)

    whether to allow multiple values. When true, accepts an array of values and repeats the option for each value. A single value or nil is also accepted. Behavior varies by output mode (see examples below).

  • required (Boolean) (defaults to: false)

    when true, the option key must be present in the provided options hash. Raises ArgumentError if the key is missing. Defaults to false.

  • allow_nil (Boolean) (defaults to: true)

    when false (with required: true), the value cannot be nil. Raises ArgumentError if a nil value is provided. Defaults to true.

Raises:

  • (ArgumentError)

    if inline: and as_operand: are both true

  • (ArgumentError)

    if defined after an end_of_options or literal '--' boundary (unless as_operand: true)



863
864
865
866
867
868
869
870
871
# File 'lib/git/commands/arguments.rb', line 863

def value_option(names, as: nil, type: nil, inline: false, as_operand: false,
                 allow_empty: false, repeatable: false, required: false, allow_nil: true)
  validate_value_modifiers!(names, inline, as_operand)

  option_type = determine_value_option_type(inline, as_operand)
  register_option(names, type: option_type, as: as, expected_type: type,
                         allow_empty: allow_empty, repeatable: repeatable, required: required,
                         allow_nil: allow_nil)
end