Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single-file mirrors + configuration versioning + cleanups #76

Merged
merged 8 commits into from
Feb 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,54 @@ Go back to tracking a particular branch.

braid diff vendor/rails

## Braid version compatibility

Since Braid has been regularly changing the configuration format and adding new
features that some projects may choose to rely on, and somewhat less often
making breaking changes in how the configuration is handled, problems can arise
if different developers work on the same project using different versions of
Braid. Since version 1.1.0, Braid refuses to operate if it detects potentially
problematic version skew. If this happens, Braid will tell you what you can do.
If you'd like an overview of what to expect, read on.

Roughly speaking, the `.braids.json` configuration file contains a configuration
version number that corresponds to a range of compatible Braid minor versions
(`x.y`). "Patch" upgrades to Braid (i.e., `x.y.z` -> `x.y.(z+1)`) will never
(intentionally!) have configuration compatibility implications and are always
recommended as they may fix critical bugs.

If you use a Braid version too old for your configuration file, Braid will
direct you to the [configuration version history page](config_versions.md) with
instructions to upgrade Braid. If you use a Braid version too new, Braid will
tell you how you can upgrade your configuration file or find a compatible older
Braid version to use. (As an exception, a newer version of Braid can run
read-only commands on an older configuration file without upgrading it if there
are no breaking changes.) If you upgrade your configuration file, then other
developers on the project may need to upgrade Braid. Braid does not support
downgrading a configuration file, though you can revert the commit that upgraded
it if you haven't made any subsequent changes to the configuration.

If you work on multiple projects, you may need to install multiple versions of
Braid and manually run the correct version for each project. Fortunately, the
RubyGems system makes this reasonably straightforward.

Another approach is to standardize the Braid version for a project by listing
Braid in a `Gemfile` (either checking in `Gemfile.lock` or using a version
constraint in the `Gemfile`) and run the project's version of Braid via
[Bundler](http://bundler.io/) with `bundle exec braid`. Even non-Ruby projects
can do this if it's acceptable to have a `Gemfile` and `Gemfile.lock`. Ruby
projects that don't want Braid to interact with their other gems can potentially
put the `Gemfile` in a subdirectory and provide a wrapper script for `bundle`
that sets the `BUNDLE_GEMFILE` environment variable. We do not yet have enough
experience with this approach to make a firm recommendation for or against it.

This is the best design we could find to prevent surprises and adequately
support normal development processes while minimizing the additional maintenance
cost of the version compatibility mechanism. We want to have a scheme in place
that is robust enough to make it reasonable to encourage serious adoption of
Braid, yet we don't want to spend extra work adding conveniences until there's
evidence of sufficient demand for them.

## Contributing

We appreciate any patches, error reports and usage ideas you may have. Please
Expand Down
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
theme: jekyll-theme-tactile
51 changes: 44 additions & 7 deletions bin/braid
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Main {
run {
check_no_extra_args!
Braid.verbose = verbose
Braid::Command.run(:add, url, {'path' => local_path, 'branch' => branch, 'tag' => tag, 'revision' => revision, 'remote_path' => path})
Braid::Command.run(:Add, url, {'path' => local_path, 'branch' => branch, 'tag' => tag, 'revision' => revision, 'remote_path' => path})
}
}

Expand Down Expand Up @@ -97,7 +97,7 @@ Main {
'keep' => keep
}
Braid.verbose = verbose
Braid::Command.run(:update, local_path, options)
Braid::Command.run(:Update, local_path, options)
}
}

Expand All @@ -122,7 +122,7 @@ Main {
:keep => keep
}
Braid.verbose = verbose
Braid::Command.run(:remove, local_path, options)
Braid::Command.run(:Remove, local_path, options)
}
}

Expand All @@ -149,7 +149,7 @@ Main {
'git_diff_args' => @argv
}
Braid.verbose = verbose
Braid::Command.run(:diff, local_path, options)
Braid::Command.run(:Diff, local_path, options)
}
}

Expand All @@ -167,7 +167,7 @@ Main {
'branch' => branch
}
Braid.verbose = verbose
Braid::Command.run(:push, local_path, options)
Braid::Command.run(:Push, local_path, options)
}
}

Expand All @@ -182,7 +182,7 @@ Main {
check_no_extra_args!
Braid.verbose = verbose
Braid.force = force
Braid::Command.run(:setup, local_path)
Braid::Command.run(:Setup, local_path)
}
}

Expand All @@ -203,7 +203,44 @@ Main {
run {
check_no_extra_args!
Braid.verbose = verbose
Braid::Command.run(:status, local_path)
Braid::Command.run(:Status, local_path)
}
}

mode("upgrade-config") {
description <<-DESC
Upgrade your project's Braid configuration to the latest configuration version.
Other commands will notify you when you need to do this. This may make older
versions of Braid unable to read the configuration and may introduce breaking
changes in how the configuration is handled by Braid. An upgrade that
introduces breaking changes will not be performed without the
--allow-breaking-changes option.
DESC

mixin :option_verbose

option("dry-run") {
optional
desc 'Explain the consequences of the upgrade without performing it.'
attr :dry_run
}

option("allow-breaking-changes") {
optional
desc <<-DESC
Perform the upgrade even if it involves breaking changes.
DESC
attr :allow_breaking_changes
}

run {
check_no_extra_args!
options = {
'dry_run' => dry_run,
'allow_breaking_changes' => allow_breaking_changes
}
Braid.verbose = verbose
Braid::Command.run(:UpgradeConfig, options)
}
}

Expand Down
58 changes: 58 additions & 0 deletions config_versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Braid configuration version history

The Braid configuration file (`.braids.json`) contains a configuration version
number that indicates the format of the configuration file and the Braid
features required by the project. You'll be directed to this page if you use a
version of Braid that does not support the project's configuration version; see
[the readme](README.md#braid-version-compatibility) for more information about
the versioning scheme.

To get a compatible version of Braid:

1. First check if the project has its own instructions to install and run Braid,
and if so, follow them instead.
2. Look up the Braid versions corresponding to your current configuration
version in the table below.
3. Run `gem query --remote --all --exact braid` to get a list of all existing
versions of Braid, and choose one that is compatible with your configuration
version (you probably want the newest such version); call it `x.y.z`.
4. Run `gem install braid --version x.y.z` to install the chosen version of
Braid.
5. Run Braid as `braid _x.y.z_` (that's the chosen version surrounded by literal
underscores) followed by your desired arguments.

<table border="border">
<tr>
<th>Config. version</th>
<th>Braid versions</th>
<th>Changes since previous</th>
</tr>
<tr>
<td>1</td>
<td>1.1.x</td>
<td>(Various)</td>
</tr>
<tr>
<td>"0"</td>
<td colspan="2">
(Braid versions earlier than 1.1.0 have varying configuration formats and
features and do not have a well-defined compatibility scheme. Braid 1.1.0 and
newer refer to all of these formats as version "0" and are capable of correctly
upgrading most of them. We recommend upgrading to Braid 1.1.0 or newer if you
can.)
</td>
</tr>
</table>

<style>
header, section#downloads, .inner > hr {
display: none;
}
.inner {
padding-top: 35px; /* same as header when it is visible */
}
th, td {
border: 1px solid #6d6d6d;
padding: 2px;
}
</style>
7 changes: 7 additions & 0 deletions lib/braid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def message
value if value != self.class.name
end
end

class InternalError < BraidError
def message
"internal error: #{super}"
end
end
end

require 'braid/operations_lite'
Expand All @@ -49,3 +55,4 @@ def message
require 'braid/commands/setup'
require 'braid/commands/update'
require 'braid/commands/status'
require 'braid/commands/upgrade_config'
14 changes: 7 additions & 7 deletions lib/braid/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def self.run(command, *args)
verify_git_version!
check_working_dir!

klass = Commands.const_get(command.to_s.capitalize)
klass = Commands.const_get(command.to_s)
klass.new.run(*args)

rescue BraidError => error
Expand All @@ -32,7 +32,7 @@ def msg(str)
end

def config
@config ||= Config.new
@config ||= Config.new({'mode' => config_mode})
end

def verbose?
Expand All @@ -45,11 +45,15 @@ def force?

private

def config_mode
Config::MODE_MAY_WRITE
end

def setup_remote(mirror)
existing_force = Braid.force
begin
Braid.force = true
Command.run(:setup, mirror.path)
Command.run(:Setup, mirror.path)
ensure
Braid.force = existing_force
end
Expand Down Expand Up @@ -133,9 +137,5 @@ def validate_new_revision(mirror, revision)
new_revision
end
end

def determine_target_revision(mirror, new_revision)
git.rev_parse(mirror.versioned_path(new_revision))
end
end
end
6 changes: 3 additions & 3 deletions lib/braid/commands/add.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ def run(url, options = {})
setup_remote(mirror)
mirror.fetch

new_revision = validate_new_revision(mirror, options['revision'])
target_revision = determine_target_revision(mirror, new_revision)
new_revision = validate_new_revision(mirror, options['revision'])
target_item = mirror.upstream_item_for_revision(new_revision)

git.read_tree_prefix_u(target_revision, mirror.path)
git.add_item_to_index(target_item, mirror.path, true)

mirror.revision = new_revision
config.update(mirror)
Expand Down
19 changes: 6 additions & 13 deletions lib/braid/commands/diff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def run(path = nil, options = {})
path ? diff_one(path, options) : diff_all(options)
end

protected
private

def diff_all(options = {})
# We don't want "git diff" to invoke the pager once for each mirror.
Expand Down Expand Up @@ -33,23 +33,16 @@ def show_diff(path, options = {})
setup_remote(mirror)
mirror.fetch_base_revision_if_missing

# We do not need to spend the time to copy the content outside the
# mirror from HEAD because --relative will exclude it anyway. Rename
# detection seems to apply only to the files included in the diff, so we
# shouldn't have another bug like
# https://github.com/cristibalan/braid/issues/41.
base_tree = git.make_tree_with_subtree(nil, mirror.path,
mirror.versioned_path(mirror.base_revision))
# Git 1.7.2.2 release notes mention a bug when --relative is used
# without a trailing slash, and our minimum git version is 1.6.0, so
# attempt to work around the bug here.
#
# XXX: Warn if the user specifies file paths that are outside the
# mirror? Currently, they just won't match anything.
git.diff_to_stdout("--relative=#{mirror.path}/", base_tree, options['git_diff_args'])
git.diff_to_stdout(*mirror.diff_args(*options['git_diff_args']))

clear_remote(mirror, options)
end

def config_mode
Config::MODE_READ_ONLY
end
end
end
end
12 changes: 8 additions & 4 deletions lib/braid/commands/push.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def run(path, options = {})
clear_remote(mirror, options)
return
end
local_mirror_tree = git.rev_parse("HEAD:#{mirror.path}")
local_mirror_item = git.get_tree_item("HEAD", mirror.path)

odb_paths = [File.expand_path(git.repo_file_path('objects'))]
if File.exist?(mirror.cached_url)
Expand Down Expand Up @@ -70,9 +70,7 @@ def run(path, options = {})
git.fetch(remote_url, mirror.remote_ref)
git.checkout(base_revision)
git.rm_r(mirror.remote_path || '.')
# Yes, when mirror.remote_path is unset, "git read-tree --prefix=/"
# seems to work. :/
git.read_tree_prefix_u(local_mirror_tree, mirror.remote_path || '')
git.add_item_to_index(local_mirror_item, mirror.remote_path || '', true)
system('git commit -v')
msg "Pushing changes to remote branch #{branch}."
git.push(remote_url, "HEAD:refs/heads/#{branch}")
Expand All @@ -82,5 +80,11 @@ def run(path, options = {})
clear_remote(mirror, options)
end
end

private

def config_mode
Config::MODE_READ_ONLY # Surprisingly enough.
end
end
end
6 changes: 5 additions & 1 deletion lib/braid/commands/setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def run(path = nil)
path ? setup_one(path) : setup_all
end

protected
private

def setup_all
msg 'Setting up all mirrors.'
Expand All @@ -31,6 +31,10 @@ def setup_one(path)
url = use_local_cache? ? git_cache.path(mirror.url) : mirror.url
git.remote_add(mirror.remote, url)
end

def config_mode
Config::MODE_READ_ONLY
end
end
end
end
Loading