diff --git a/README.md b/README.md index 540567f..8c9753c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bin/braid b/bin/braid index 47e4867..2eec40b 100755 --- a/bin/braid +++ b/bin/braid @@ -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}) } } @@ -97,7 +97,7 @@ Main { 'keep' => keep } Braid.verbose = verbose - Braid::Command.run(:update, local_path, options) + Braid::Command.run(:Update, local_path, options) } } @@ -122,7 +122,7 @@ Main { :keep => keep } Braid.verbose = verbose - Braid::Command.run(:remove, local_path, options) + Braid::Command.run(:Remove, local_path, options) } } @@ -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) } } @@ -167,7 +167,7 @@ Main { 'branch' => branch } Braid.verbose = verbose - Braid::Command.run(:push, local_path, options) + Braid::Command.run(:Push, local_path, options) } } @@ -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) } } @@ -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) } } diff --git a/config_versions.md b/config_versions.md new file mode 100644 index 0000000..335ff00 --- /dev/null +++ b/config_versions.md @@ -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. + + + + + + + + + + + + + + + + +
Config. versionBraid versionsChanges since previous
11.1.x(Various)
"0" +(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.) +
+ + diff --git a/lib/braid.rb b/lib/braid.rb index 4dbfa0e..6a126a2 100644 --- a/lib/braid.rb +++ b/lib/braid.rb @@ -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' @@ -49,3 +55,4 @@ def message require 'braid/commands/setup' require 'braid/commands/update' require 'braid/commands/status' +require 'braid/commands/upgrade_config' diff --git a/lib/braid/command.rb b/lib/braid/command.rb index 3de7a42..0ca2e76 100644 --- a/lib/braid/command.rb +++ b/lib/braid/command.rb @@ -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 @@ -32,7 +32,7 @@ def msg(str) end def config - @config ||= Config.new + @config ||= Config.new({'mode' => config_mode}) end def verbose? @@ -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 diff --git a/lib/braid/commands/diff.rb b/lib/braid/commands/diff.rb index 62fec80..7794ecf 100644 --- a/lib/braid/commands/diff.rb +++ b/lib/braid/commands/diff.rb @@ -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. @@ -39,6 +39,10 @@ def show_diff(path, options = {}) clear_remote(mirror, options) end + + def config_mode + Config::MODE_READ_ONLY + end end end end diff --git a/lib/braid/commands/push.rb b/lib/braid/commands/push.rb index 5b9cb96..b052104 100644 --- a/lib/braid/commands/push.rb +++ b/lib/braid/commands/push.rb @@ -80,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 diff --git a/lib/braid/commands/setup.rb b/lib/braid/commands/setup.rb index b1a2c22..152f18a 100644 --- a/lib/braid/commands/setup.rb +++ b/lib/braid/commands/setup.rb @@ -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.' @@ -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 diff --git a/lib/braid/commands/status.rb b/lib/braid/commands/status.rb index 478d56a..3ce94c4 100644 --- a/lib/braid/commands/status.rb +++ b/lib/braid/commands/status.rb @@ -5,7 +5,7 @@ def run(path = nil, options = {}) path ? status_one(path, options) : status_all(options) end - protected + private def status_all(options = {}) print "\n" @@ -41,6 +41,10 @@ def status_one(path, options = {}) print "\n" clear_remote(mirror, options) end + + def config_mode + Config::MODE_READ_ONLY + end end end end diff --git a/lib/braid/commands/upgrade_config.rb b/lib/braid/commands/upgrade_config.rb new file mode 100644 index 0000000..a95f697 --- /dev/null +++ b/lib/braid/commands/upgrade_config.rb @@ -0,0 +1,56 @@ +module Braid + module Commands + class UpgradeConfig < Command + def config_mode + Config::MODE_UPGRADE + end + + def run(options) + # Config loading in MODE_UPGRADE will bail out only if the config + # version is too new. + + if !config.config_existed + puts <<-MSG +Your repository has no Braid configuration file. It will be created with the +current configuration version when you add the first mirror. +MSG + return + elsif config.config_version == Config::CURRENT_CONFIG_VERSION + puts <<-MSG +Your configuration file is already at the current configuration version (#{Config::CURRENT_CONFIG_VERSION}). +MSG + return + end + + puts <<-MSG +Your configuration file will be upgraded from configuration version #{config.config_version} to #{Config::CURRENT_CONFIG_VERSION}. +Other developers on your project will need to use a Braid version compatible +with configuration version #{Config::CURRENT_CONFIG_VERSION}; see +https://cristibalan.github.io/braid/config_versions.html . + +MSG + + unless config.breaking_change_descs.empty? + puts <<-MSG +The following breaking changes will occur: +#{config.breaking_change_descs.join('')} +MSG + end + + if options['dry_run'] + puts <<-MSG +Run 'braid upgrade-config#{config.breaking_change_descs.empty? ? '' : ' --allow-breaking-changes'}' to perform the upgrade. +MSG + elsif !config.breaking_change_descs.empty? && !options['allow_breaking_changes'] + raise BraidError, "You must pass --allow-breaking-changes to accept the breaking changes." + else + config.write_db + add_config_file + had_changes = git.commit("Upgrade configuration") + raise InternalError, "upgrade-config had no changes??" unless had_changes + msg "Configuration upgrade complete." + end + end + end + end +end diff --git a/lib/braid/config.rb b/lib/braid/config.rb index 5271f2f..05387f6 100644 --- a/lib/braid/config.rb +++ b/lib/braid/config.rb @@ -2,8 +2,59 @@ require 'json' require 'yaml/store' +# Some info about the configuration versioning design: +# https://github.com/cristibalan/braid/issues/66#issuecomment-354211311 +# +# Current configuration format: +# ``` +# { +# "config_version": 1, +# "mirrors": { +# : { +# "url": , +# "path": , +# "branch": , +# "tag": , +# "revision": +# } +# } +# } +# ``` +# +# History of configuration formats understood by current Braid: +# +# - Braid 1.1.0, config_version 1: +# - "config_version" introduced; mirrors moved to "mirrors" +# - Single-file mirrors (f340b0c) +# - Braid 1.0.18: +# - Locked mirrors indicated by absence of "branch" and "tag" attributes, not +# presence of "lock" attribute (e6535aa) +# - Braid 1.0.17: +# - Support for full-history mirrors ("squashed": false) removed; "squashed" +# attribute no longer written (eb72030) +# - Braid 1.0.11: +# - "remote" attribute no longer written (f8fd088) +# - Braid 1.0.9: +# - .braids -> .braids.json (6806c61) +# - Braid 1.0.0: +# - YAML -> JSON (9d3fa11) +# - Support for Subversion mirrors removed ("type": "svn") removed (9d8d390) +# +# +# (Entries that predate the creation of this list have commit IDs for reference. +# Of course, when adding a new entry, you can't add the commit ID in the same +# commit, but you don't need to because people can just run `git log` on this +# file.) + module Braid class Config + + MODE_UPGRADE = 1 + MODE_READ_ONLY = 2 + MODE_MAY_WRITE = 3 + + CURRENT_CONFIG_VERSION = 1 + class PathAlreadyInUse < BraidError def message "path already in use: #{super}" @@ -15,25 +66,88 @@ def message end end - def initialize(config_file = CONFIG_FILE, old_config_files = [OLD_CONFIG_FILE]) - @config_file = config_file - (old_config_files + [config_file]).each do |file| - next unless File.exist?(file) + class RemoveMirrorDueToBreakingChange < StandardError + end + + # For upgrade-config command only. XXX: Ideally would be immutable. + attr_reader :config_version, :config_existed, :breaking_change_descs + + # options: config_file, old_config_files, mode + def initialize(options = {}) + @config_file = options['config_file'] || CONFIG_FILE + old_config_files = options['old_config_files'] || [OLD_CONFIG_FILE] + @mode = options['mode'] || MODE_MAY_WRITE + + data = load_config(@config_file, old_config_files) + @config_existed = !data.nil? + if !@config_existed + @config_version = CURRENT_CONFIG_VERSION + @db = {} + elsif data['config_version'].is_a?(Numeric) + @config_version = data['config_version'] + @db = data['mirrors'] + else + # Before config versioning (Braid < 1.1.0) + @config_version = 0 + @db = data + end + + if @config_version > CURRENT_CONFIG_VERSION + raise BraidError, <<-MSG +This version of Braid (#{VERSION}) is too old to understand your project's Braid +configuration file (version #{@config_version}). See the instructions at +https://cristibalan.github.io/braid/config_versions.html to install and use a +compatible newer version of Braid. +MSG + end + + # In all modes, instantiate all mirrors to scan for breaking changes. + @breaking_change_descs = [] + paths_to_delete = [] + @db.each do |path, attributes| begin - store = YAML::Store.new(file) - @db = {} - store.transaction(true) do - store.roots.each do |path| - @db[path] = store[path] - end - end - return - rescue - @db = JSON.parse(file) - return if @db + mirror = Mirror.new(path, attributes, + lambda {|desc| @breaking_change_descs.push(desc)}) + # In MODE_UPGRADE, update @db now. In other modes, we won't write the + # config if an upgrade is needed, so it doesn't matter that we don't + # update @db. + # + # It's OK to change the values of existing keys during iteration: + # https://groups.google.com/d/msg/comp.lang.ruby/r5OI6UaxAAg/SVpU0cktmZEJ + write_mirror(mirror) if @mode == MODE_UPGRADE + rescue RemoveMirrorDueToBreakingChange + # I don't know if deleting during iteration is well-defined in all + # Ruby versions we support, so defer the deletion. + # ~ matt@mattmccutchen.net, 2017-12-31 + paths_to_delete.push(path) if @mode == MODE_UPGRADE end end - @db = {} + paths_to_delete.each do |path| + @db.delete(path) + end + + if @mode != MODE_UPGRADE && !@breaking_change_descs.empty? + raise BraidError, <<-MSG +This version of Braid (#{VERSION}) no longer supports a feature used by your +Braid configuration file (version #{@config_version}). Run 'braid upgrade-config --dry-run' +for information about upgrading your configuration file, or see the instructions +at https://cristibalan.github.io/braid/config_versions.html to install and run a +compatible older version of Braid. +MSG + end + + if @mode == MODE_MAY_WRITE && @config_version < CURRENT_CONFIG_VERSION + raise BraidError, <<-MSG +This command may need to write to your Braid configuration file, +but this version of Braid (#{VERSION}) cannot write to your configuration file +(currently version #{config_version}) without upgrading it to configuration version #{CURRENT_CONFIG_VERSION}, +which would force other developers on your project to upgrade Braid. Run +'braid upgrade-config' to proceed with the upgrade, or see the instructions at +https://cristibalan.github.io/braid/config_versions.html to install and run a +compatible older version of Braid. +MSG + end + end def add_from_options(url, options) @@ -62,6 +176,7 @@ def get!(path) def add(mirror) raise PathAlreadyInUse, mirror.path if get(mirror.path) write_mirror(mirror) + write_db end def remove(mirror) @@ -71,31 +186,55 @@ def remove(mirror) def update(mirror) raise MirrorDoesNotExist, mirror.path unless get(mirror.path) - @db.delete(mirror.path) write_mirror(mirror) - end - - private - - def write_mirror(mirror) - @db[mirror.path] = clean_attributes(mirror.attributes) write_db end + # Public for upgrade-config command only. def write_db new_db = {} @db.keys.sort.each do |key| - new_db[key] = @db[key] - new_db[key].keys.each do |k| - new_db[key].delete(k) unless Braid::Mirror::ATTRIBUTES.include?(k) + new_db[key] = {} + Braid::Mirror::ATTRIBUTES.each do |k| + new_db[key][k] = @db[key][k] if @db[key].has_key?(k) end end + new_data = { + 'config_version' => CURRENT_CONFIG_VERSION, + 'mirrors' => new_db + } File.open(@config_file, 'wb') do |f| - f.write JSON.pretty_generate(new_db) + f.write JSON.pretty_generate(new_data) f.write "\n" end end + private + + def load_config(config_file, old_config_files) + (old_config_files + [config_file]).each do |file| + next unless File.exist?(file) + begin + store = YAML::Store.new(file) + data = {} + store.transaction(true) do + store.roots.each do |path| + data[path] = store[path] + end + end + return data + rescue + data = JSON.parse(file) + return data if data + end + end + return nil + end + + def write_mirror(mirror) + @db[mirror.path] = clean_attributes(mirror.attributes) + end + def clean_attributes(hash) hash.reject { |k, v| v.nil? } end diff --git a/lib/braid/mirror.rb b/lib/braid/mirror.rb index e233f8f..1616c54 100644 --- a/lib/braid/mirror.rb +++ b/lib/braid/mirror.rb @@ -1,6 +1,9 @@ module Braid class Mirror - ATTRIBUTES = %w(url branch revision tag path) + # Since Braid 1.1.0, the attributes are written to .braids.json in this + # canonical order. For now, the order is chosen to match what Braid 1.0.22 + # produced for newly added mirrors. + ATTRIBUTES = %w(url branch path tag revision) class UnknownType < BraidError def message @@ -22,9 +25,44 @@ def message attr_reader :path, :attributes - def initialize(path, attributes = {}) + def initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB) @path = path.sub(/\/$/, '') - @attributes = attributes + @attributes = attributes.dup + + # Not that it's terribly important to check for such an old feature. This + # is mainly to demonstrate the RemoveMirrorDueToBreakingChange mechanism + # in case we want to use it for something else in the future. + if !@attributes['type'].nil? && @attributes['type'] != 'git' + breaking_change_cb.call <<-DESC +- Mirror '#{path}' is of a Subversion repository, which is no + longer supported. The mirror will be removed from your configuration, leaving + the data in the tree. +DESC + raise Config::RemoveMirrorDueToBreakingChange + end + @attributes.delete('type') + + # Migrate revision locks from Braid < 1.0.18. We no longer store the + # original branch or tag (the user has to specify it again when + # unlocking); we simply represent a locked revision by the absence of a + # branch or tag. + if @attributes['lock'] + @attributes.delete('lock') + @attributes['branch'] = nil + @attributes['tag'] = nil + end + + # Removal of support for full-history mirrors from Braid < 1.0.17 is a + # breaking change for users who wanted to use the imported history in some + # way. + if !@attributes['squashed'].nil? && @attributes['squashed'] != true + breaking_change_cb.call <<-DESC +- Mirror '#{path}' is full-history, which is no longer supported. + It will be changed to squashed. Upstream history already imported will remain + in your project's history and will have no effect on Braid. +DESC + end + @attributes.delete('squashed') end def self.new_from_options(url, options = {}) @@ -183,6 +221,11 @@ def remote private + DUMMY_BREAKING_CHANGE_CB = lambda { |desc| + raise InternalError, "Instantiated a mirror using an unsupported " + + "feature outside of configuration loading." + } + def method_missing(name, *args) if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ } if $2 diff --git a/lib/braid/version.rb b/lib/braid/version.rb index 7bfcf1c..92a56e8 100644 --- a/lib/braid/version.rb +++ b/lib/braid/version.rb @@ -1,3 +1,3 @@ module Braid - VERSION = '1.0.22'.freeze + VERSION = '1.1.0'.freeze end diff --git a/spec/config_spec.rb b/spec/config_spec.rb index aa75eb2..1f50681 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -2,7 +2,7 @@ describe 'Braid::Config, when empty' do before(:each) do - @config = Braid::Config.new('tmp.yml') + @config = Braid::Config.new({'config_file' => 'tmp.yml'}) end after(:each) do @@ -23,7 +23,7 @@ describe 'Braid::Config, with one mirror' do before(:each) do - @config = Braid::Config.new('tmp.yml') + @config = Braid::Config.new({'config_file' => 'tmp.yml'}) @mirror = build_mirror @config.add(@mirror) end diff --git a/spec/fixtures/shiny-conf-1.0.9-lock/.braids.json b/spec/fixtures/shiny-conf-1.0.9-lock/.braids.json new file mode 100644 index 0000000..f11fdb5 --- /dev/null +++ b/spec/fixtures/shiny-conf-1.0.9-lock/.braids.json @@ -0,0 +1,10 @@ +{ + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1", + "remote": "master/braid/skit1", + "branch": "master", + "squashed": true, + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176", + "lock": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } +} diff --git a/spec/fixtures/shiny-conf-1.0.9-lock/expected.braids.json b/spec/fixtures/shiny-conf-1.0.9-lock/expected.braids.json new file mode 100644 index 0000000..51813ef --- /dev/null +++ b/spec/fixtures/shiny-conf-1.0.9-lock/expected.braids.json @@ -0,0 +1,9 @@ +{ + "config_version": 1, + "mirrors": { + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1", + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } + } +} diff --git a/spec/fixtures/shiny-conf-1.0.9-lock/skit1/layouts/layout.liquid b/spec/fixtures/shiny-conf-1.0.9-lock/skit1/layouts/layout.liquid new file mode 100644 index 0000000..9f75009 --- /dev/null +++ b/spec/fixtures/shiny-conf-1.0.9-lock/skit1/layouts/layout.liquid @@ -0,0 +1,219 @@ + + +{{ '/feed/atom.xml' | assign_to: 'global_feed' }} +{{ '/feed/all_comments.xml' | assign_to: 'global_comments_feed' }} + + + + {{ site.title }} + {% if site.current_section %} + - {{ site.current_section.name }} + {% endif %} + {% if article %} + - {{ article.title }} + {% endif %} + + + +{{ 'base' | stylesheet }} +{{ 'app' | javascript }} + + + + + + +
+ + + +
+ +
+ +
+ +
+
+

Skittlish Tips'n'Tricks

+
    +
  • Change the classes for the body tag to set your default site style. also change these in the app.js file (line 66).
  • +
  • Remove the scripts and the #options div if you don't want the option switcher.
  • +
  • The top menu shows the home section and the sections that are not blogs.
  • +
  • Email me at evil@che.lu if you have any questions.
  • +
  • Happy hacking!
  • +
+
+
+ +{{ content_for_layout }} + +
+ +
+ + + +
+ +
+

Options:

+

Size

+ +

Colors

+ +
+ + + +
+ + + + diff --git a/spec/fixtures/shiny-conf-1.0.9-lock/skit1/preview.png b/spec/fixtures/shiny-conf-1.0.9-lock/skit1/preview.png new file mode 100644 index 0000000..f7d1a3e Binary files /dev/null and b/spec/fixtures/shiny-conf-1.0.9-lock/skit1/preview.png differ diff --git a/spec/fixtures/shiny-conf-breaking-changes/.braids b/spec/fixtures/shiny-conf-breaking-changes/.braids new file mode 100644 index 0000000..935a8a1 --- /dev/null +++ b/spec/fixtures/shiny-conf-breaking-changes/.braids @@ -0,0 +1,14 @@ +--- !ruby/object:Hash +Spoon-Knife: !ruby/object:Hash + remote: master/braid/Spoon-Knife + revision: 18 + squashed: true + type: svn + url: https://github.com/octocat/Spoon-Knife.git/trunk +skit1: !ruby/object:Hash + branch: master + remote: master/braid/skit1 + revision: 6d3aeac08f9f4f9689d367fc771f5f1c90496176 + squashed: false + type: git + url: file:///path/to/braid/spec/fixtures/skit1/.git diff --git a/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/README.md b/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/README.md new file mode 100644 index 0000000..f479026 --- /dev/null +++ b/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/README.md @@ -0,0 +1,9 @@ +### Well hello there! + +This repository is meant to provide an example for *forking* a repository on GitHub. + +Creating a *fork* is producing a personal copy of someone else's project. Forks act as a sort of bridge between the original repository and your personal copy. You can submit *Pull Requests* to help make other people's projects better by offering your changes up to the original project. Forking is at the core of social coding at GitHub. + +After forking this repository, you can make some changes to the project, and submit [a Pull Request](https://github.com/octocat/Spoon-Knife/pulls) as practice. + +For some more information on how to fork a repository, [check out our guide, "Forking Projects""](http://guides.github.com/overviews/forking/). Thanks! :sparkling_heart: diff --git a/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/index.html b/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/index.html new file mode 100644 index 0000000..a83618b --- /dev/null +++ b/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/index.html @@ -0,0 +1,20 @@ + + + + + + Spoon-Knife + + + + + + + + +

+ Fork me? Fork you, @octocat! +

+ + + diff --git a/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/styles.css b/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/styles.css new file mode 100644 index 0000000..9b85284 --- /dev/null +++ b/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/styles.css @@ -0,0 +1,17 @@ +* { + margin:0px; + padding:0px; +} + +#octocat { + display: block; + width:384px; + margin: 50px auto; +} + +p { + display: block; + width: 400px; + margin: 50px auto; + font: 30px Monaco,"Courier New","DejaVu Sans Mono","Bitstream Vera Sans Mono",monospace; +} diff --git a/spec/fixtures/shiny-conf-breaking-changes/expected.braids.json b/spec/fixtures/shiny-conf-breaking-changes/expected.braids.json new file mode 100644 index 0000000..5e3f9e4 --- /dev/null +++ b/spec/fixtures/shiny-conf-breaking-changes/expected.braids.json @@ -0,0 +1,10 @@ +{ + "config_version": 1, + "mirrors": { + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1/.git", + "branch": "master", + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } + } +} diff --git a/spec/fixtures/shiny-conf-breaking-changes/skit1/layouts/layout.liquid b/spec/fixtures/shiny-conf-breaking-changes/skit1/layouts/layout.liquid new file mode 100644 index 0000000..9f75009 --- /dev/null +++ b/spec/fixtures/shiny-conf-breaking-changes/skit1/layouts/layout.liquid @@ -0,0 +1,219 @@ + + +{{ '/feed/atom.xml' | assign_to: 'global_feed' }} +{{ '/feed/all_comments.xml' | assign_to: 'global_comments_feed' }} + + + + {{ site.title }} + {% if site.current_section %} + - {{ site.current_section.name }} + {% endif %} + {% if article %} + - {{ article.title }} + {% endif %} + + + +{{ 'base' | stylesheet }} +{{ 'app' | javascript }} + + + + + + +
+ + + +
+ +
+ +
+ +
+
+

Skittlish Tips'n'Tricks

+
    +
  • Change the classes for the body tag to set your default site style. also change these in the app.js file (line 66).
  • +
  • Remove the scripts and the #options div if you don't want the option switcher.
  • +
  • The top menu shows the home section and the sections that are not blogs.
  • +
  • Email me at evil@che.lu if you have any questions.
  • +
  • Happy hacking!
  • +
+
+
+ +{{ content_for_layout }} + +
+ +
+ + + +
+ +
+

Options:

+

Size

+ +

Colors

+ +
+ + + +
+ + + + diff --git a/spec/fixtures/shiny-conf-breaking-changes/skit1/preview.png b/spec/fixtures/shiny-conf-breaking-changes/skit1/preview.png new file mode 100644 index 0000000..f7d1a3e Binary files /dev/null and b/spec/fixtures/shiny-conf-breaking-changes/skit1/preview.png differ diff --git a/spec/fixtures/shiny-conf-future/.braids.json b/spec/fixtures/shiny-conf-future/.braids.json new file mode 100644 index 0000000..a0fef60 --- /dev/null +++ b/spec/fixtures/shiny-conf-future/.braids.json @@ -0,0 +1,10 @@ +{ + "config_version": 999, + "mirrors": { + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1", + "branch": "master", + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } + } +} diff --git a/spec/fixtures/shiny-conf-future/skit1/layouts/layout.liquid b/spec/fixtures/shiny-conf-future/skit1/layouts/layout.liquid new file mode 100644 index 0000000..9f75009 --- /dev/null +++ b/spec/fixtures/shiny-conf-future/skit1/layouts/layout.liquid @@ -0,0 +1,219 @@ + + +{{ '/feed/atom.xml' | assign_to: 'global_feed' }} +{{ '/feed/all_comments.xml' | assign_to: 'global_comments_feed' }} + + + + {{ site.title }} + {% if site.current_section %} + - {{ site.current_section.name }} + {% endif %} + {% if article %} + - {{ article.title }} + {% endif %} + + + +{{ 'base' | stylesheet }} +{{ 'app' | javascript }} + + + + + + +
+ + + +
+ +
+ +
+ +
+
+

Skittlish Tips'n'Tricks

+
    +
  • Change the classes for the body tag to set your default site style. also change these in the app.js file (line 66).
  • +
  • Remove the scripts and the #options div if you don't want the option switcher.
  • +
  • The top menu shows the home section and the sections that are not blogs.
  • +
  • Email me at evil@che.lu if you have any questions.
  • +
  • Happy hacking!
  • +
+
+
+ +{{ content_for_layout }} + +
+ +
+ + + +
+ +
+

Options:

+

Size

+ +

Colors

+ +
+ + + +
+ + + + diff --git a/spec/fixtures/shiny-conf-future/skit1/preview.png b/spec/fixtures/shiny-conf-future/skit1/preview.png new file mode 100644 index 0000000..f7d1a3e Binary files /dev/null and b/spec/fixtures/shiny-conf-future/skit1/preview.png differ diff --git a/spec/fixtures/shiny-conf-json-old-name/.braids b/spec/fixtures/shiny-conf-json-old-name/.braids new file mode 100644 index 0000000..2acd2fd --- /dev/null +++ b/spec/fixtures/shiny-conf-json-old-name/.braids @@ -0,0 +1,9 @@ +{ + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1", + "remote": "master/braid/skit1", + "branch": "master", + "squashed": true, + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } +} diff --git a/spec/fixtures/shiny-conf-json-old-name/expected.braids.json b/spec/fixtures/shiny-conf-json-old-name/expected.braids.json new file mode 100644 index 0000000..31523be --- /dev/null +++ b/spec/fixtures/shiny-conf-json-old-name/expected.braids.json @@ -0,0 +1,10 @@ +{ + "config_version": 1, + "mirrors": { + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1", + "branch": "master", + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } + } +} diff --git a/spec/fixtures/shiny-conf-json-old-name/skit1/layouts/layout.liquid b/spec/fixtures/shiny-conf-json-old-name/skit1/layouts/layout.liquid new file mode 100644 index 0000000..9f75009 --- /dev/null +++ b/spec/fixtures/shiny-conf-json-old-name/skit1/layouts/layout.liquid @@ -0,0 +1,219 @@ + + +{{ '/feed/atom.xml' | assign_to: 'global_feed' }} +{{ '/feed/all_comments.xml' | assign_to: 'global_comments_feed' }} + + + + {{ site.title }} + {% if site.current_section %} + - {{ site.current_section.name }} + {% endif %} + {% if article %} + - {{ article.title }} + {% endif %} + + + +{{ 'base' | stylesheet }} +{{ 'app' | javascript }} + + + + + + +
+ + + +
+ +
+ +
+ +
+
+

Skittlish Tips'n'Tricks

+
    +
  • Change the classes for the body tag to set your default site style. also change these in the app.js file (line 66).
  • +
  • Remove the scripts and the #options div if you don't want the option switcher.
  • +
  • The top menu shows the home section and the sections that are not blogs.
  • +
  • Email me at evil@che.lu if you have any questions.
  • +
  • Happy hacking!
  • +
+
+
+ +{{ content_for_layout }} + +
+ +
+ + + +
+ +
+

Options:

+

Size

+ +

Colors

+ +
+ + + +
+ + + + diff --git a/spec/fixtures/shiny-conf-json-old-name/skit1/preview.png b/spec/fixtures/shiny-conf-json-old-name/skit1/preview.png new file mode 100644 index 0000000..f7d1a3e Binary files /dev/null and b/spec/fixtures/shiny-conf-json-old-name/skit1/preview.png differ diff --git a/spec/fixtures/shiny-conf-yaml/.braids b/spec/fixtures/shiny-conf-yaml/.braids new file mode 100644 index 0000000..eb98a4e --- /dev/null +++ b/spec/fixtures/shiny-conf-yaml/.braids @@ -0,0 +1,8 @@ +--- !ruby/object:Hash +skit1: !ruby/object:Hash + branch: master + remote: master/braid/skit1 + revision: 6d3aeac08f9f4f9689d367fc771f5f1c90496176 + squashed: true + type: git + url: file:///path/to/braid/spec/fixtures/skit1/.git diff --git a/spec/fixtures/shiny-conf-yaml/expected.braids.json b/spec/fixtures/shiny-conf-yaml/expected.braids.json new file mode 100644 index 0000000..5e3f9e4 --- /dev/null +++ b/spec/fixtures/shiny-conf-yaml/expected.braids.json @@ -0,0 +1,10 @@ +{ + "config_version": 1, + "mirrors": { + "skit1": { + "url": "file:///path/to/braid/spec/fixtures/skit1/.git", + "branch": "master", + "revision": "6d3aeac08f9f4f9689d367fc771f5f1c90496176" + } + } +} diff --git a/spec/fixtures/shiny-conf-yaml/skit1/layouts/layout.liquid b/spec/fixtures/shiny-conf-yaml/skit1/layouts/layout.liquid new file mode 100644 index 0000000..9f75009 --- /dev/null +++ b/spec/fixtures/shiny-conf-yaml/skit1/layouts/layout.liquid @@ -0,0 +1,219 @@ + + +{{ '/feed/atom.xml' | assign_to: 'global_feed' }} +{{ '/feed/all_comments.xml' | assign_to: 'global_comments_feed' }} + + + + {{ site.title }} + {% if site.current_section %} + - {{ site.current_section.name }} + {% endif %} + {% if article %} + - {{ article.title }} + {% endif %} + + + +{{ 'base' | stylesheet }} +{{ 'app' | javascript }} + + + + + + +
+ + + +
+ +
+ +
+ +
+
+

Skittlish Tips'n'Tricks

+
    +
  • Change the classes for the body tag to set your default site style. also change these in the app.js file (line 66).
  • +
  • Remove the scripts and the #options div if you don't want the option switcher.
  • +
  • The top menu shows the home section and the sections that are not blogs.
  • +
  • Email me at evil@che.lu if you have any questions.
  • +
  • Happy hacking!
  • +
+
+
+ +{{ content_for_layout }} + +
+ +
+ + + +
+ +
+

Options:

+

Size

+ +

Colors

+ +
+ + + +
+ + + + diff --git a/spec/fixtures/shiny-conf-yaml/skit1/preview.png b/spec/fixtures/shiny-conf-yaml/skit1/preview.png new file mode 100644 index 0000000..f7d1a3e Binary files /dev/null and b/spec/fixtures/shiny-conf-yaml/skit1/preview.png differ diff --git a/spec/integration/adding_spec.rb b/spec/integration/adding_spec.rb index 0b2d33c..e8ae1cc 100644 --- a/spec/integration/adding_spec.rb +++ b/spec/integration/adding_spec.rb @@ -29,11 +29,13 @@ it 'should create .braids.json and add the mirror to it' do braids = YAML::load_file("#{@repository_dir}/.braids.json") - expect(braids['skit1']['url']).to eq(@vendor_repository_dir) - expect(braids['skit1']['revision']).not_to be_nil - expect(braids['skit1']['branch']).to eq('master') - expect(braids['skit1']['tag']).to be_nil - expect(braids['skit1']['path']).to be_nil + expect(braids['config_version']).to be_kind_of(Numeric) + mirror_obj = braids['mirrors']['skit1'] + expect(mirror_obj['url']).to eq(@vendor_repository_dir) + expect(mirror_obj['revision']).not_to be_nil + expect(mirror_obj['branch']).to eq('master') + expect(mirror_obj['tag']).to be_nil + expect(mirror_obj['path']).to be_nil end end @@ -59,11 +61,13 @@ it 'should create .braids.json and add the mirror to it' do braids = YAML::load_file("#{@repository_dir}/.braids.json") - expect(braids['skit-layouts']['url']).to eq(@vendor_repository_dir) - expect(braids['skit-layouts']['revision']).not_to be_nil - expect(braids['skit-layouts']['branch']).to eq('master') - expect(braids['skit-layouts']['tag']).to be_nil - expect(braids['skit-layouts']['path']).to eq('layouts') + expect(braids['config_version']).to be_kind_of(Numeric) + mirror_obj = braids['mirrors']['skit-layouts'] + expect(mirror_obj['url']).to eq(@vendor_repository_dir) + expect(mirror_obj['revision']).not_to be_nil + expect(mirror_obj['branch']).to eq('master') + expect(mirror_obj['tag']).to be_nil + expect(mirror_obj['path']).to eq('layouts') end end @@ -89,11 +93,13 @@ it 'should create .braids.json and add the mirror to it' do braids = YAML::load_file("#{@repository_dir}/.braids.json") - expect(braids['skit-layout.liquid']['url']).to eq(@vendor_repository_dir) - expect(braids['skit-layout.liquid']['revision']).not_to be_nil - expect(braids['skit-layout.liquid']['branch']).to eq('master') - expect(braids['skit-layout.liquid']['tag']).to be_nil - expect(braids['skit-layout.liquid']['path']).to eq('layouts/layout.liquid') + expect(braids['config_version']).to be_kind_of(Numeric) + mirror_obj = braids['mirrors']['skit-layout.liquid'] + expect(mirror_obj['url']).to eq(@vendor_repository_dir) + expect(mirror_obj['revision']).not_to be_nil + expect(mirror_obj['branch']).to eq('master') + expect(mirror_obj['tag']).to be_nil + expect(mirror_obj['path']).to eq('layouts/layout.liquid') end end @@ -122,11 +128,13 @@ it 'should create .braids.json and add the mirror to it' do braids = YAML::load_file("#{@repository_dir}/.braids.json") - expect(braids['skit1']['url']).to eq(@vendor_repository_dir) - expect(braids['skit1']['revision']).not_to be_nil - expect(braids['skit1']['branch']).to be_nil - expect(braids['skit1']['tag']).to eq('v1') - expect(braids['skit1']['path']).to be_nil + expect(braids['config_version']).to be_kind_of(Numeric) + mirror_obj = braids['mirrors']['skit1'] + expect(mirror_obj['url']).to eq(@vendor_repository_dir) + expect(mirror_obj['revision']).not_to be_nil + expect(mirror_obj['branch']).to be_nil + expect(mirror_obj['tag']).to eq('v1') + expect(mirror_obj['path']).to be_nil end end @@ -156,11 +164,13 @@ it 'should create .braids.json and add the mirror to it' do braids = YAML::load_file("#{@repository_dir}/.braids.json") - expect(braids['skit1']['url']).to eq(@vendor_repository_dir) - expect(braids['skit1']['revision']).not_to be_nil - expect(braids['skit1']['branch']).to be_nil - expect(braids['skit1']['tag']).to be_nil - expect(braids['skit1']['path']).to be_nil + expect(braids['config_version']).to be_kind_of(Numeric) + mirror_obj = braids['mirrors']['skit1'] + expect(mirror_obj['url']).to eq(@vendor_repository_dir) + expect(mirror_obj['revision']).not_to be_nil + expect(mirror_obj['branch']).to be_nil + expect(mirror_obj['tag']).to be_nil + expect(mirror_obj['path']).to be_nil end end diff --git a/spec/integration/config_versioning_spec.rb b/spec/integration/config_versioning_spec.rb new file mode 100644 index 0000000..82e03c4 --- /dev/null +++ b/spec/integration/config_versioning_spec.rb @@ -0,0 +1,221 @@ +require File.dirname(__FILE__) + '/integration_helper' + +describe 'Config versioning:' do + + before do + FileUtils.rm_rf(TMP_PATH) + FileUtils.mkdir_p(TMP_PATH) + end + + # Workaround for Braid writing .braids.json with LF line endings on Windows, + # while the .braids.json files in the fixtures get converted to CRLF under Git + # for Windows recommended settings. + # https://github.com/cristibalan/braid/issues/77 + def assert_no_diff_in_braids(file1, file2) + assert_no_diff(file1, file2, "--ignore-trailing-space") + end + + describe 'read-only command' do + + it "from future config version should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-future') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} diff skit1") + expect(output).to match(/is too old to understand/) + end + end + + it "from old config version with no breaking changes should work" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-yaml') + @vendor_repository_dir = create_git_repo_from_fixture('skit1') + + vendor_revision = nil + in_dir(@vendor_repository_dir) do + vendor_revision = run_command("git rev-parse HEAD") + end + + in_dir(@repository_dir) do + # For a real command to work, we have to substitute the URL and revision + # of the real vendor repository we created on this run of the test. The + # below looks marginally easier than using the real YAML parser. + braids_content = nil + File.open('.braids', 'rb') do |f| + braids_content = f.read + end + braids_content = braids_content.sub(/revision:.*$/, "revision: #{vendor_revision}") + braids_content = braids_content.sub(/url:.*$/, "url: file://#{@vendor_repository_dir}") + File.open('.braids', 'wb') do |f| + f.write braids_content + end + + output = run_command("#{BRAID_BIN} diff skit1") + expect(output).to eq('') # no diff + end + end + + it "from old config version with breaking changes should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-breaking-changes') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} diff skit1") + expect(output).to match(/no longer supports a feature/) + end + end + + end + + describe 'write command' do + + it "from future config version should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-future') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} update skit1") + expect(output).to match(/is too old to understand/) + end + end + + it "from old config version with no breaking changes should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-yaml') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} update skit1") + expect(output).to match(/force other developers on your project to upgrade Braid/) + end + end + + it "from old config version with breaking changes should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-breaking-changes') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} update skit1") + expect(output).to match(/no longer supports a feature/) + end + end + + end + + describe '"braid upgrade-config"' do + + it "from Braid 0.7.1 (.braids YAML) should produce the expected configuration" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-yaml') + + in_dir(@repository_dir) do + output = run_command("#{BRAID_BIN} upgrade-config") + # Check this on one of the test cases. + expect(output).to match(/Configuration upgrade complete\./) + expect(File.exists?(".braids")).to eq(false) + assert_no_diff_in_braids(".braids.json", "expected.braids.json") + end + end + + it "from Braid 1.0.0 (.braids JSON) should produce the expected configuration" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-json-old-name') + + in_dir(@repository_dir) do + run_command("#{BRAID_BIN} upgrade-config") + expect(File.exists?(".braids")).to eq(false) + assert_no_diff_in_braids(".braids.json", "expected.braids.json") + end + end + + it "from Braid 1.0.9 (.braids.json) with old-style lock should produce the expected configuration" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-1.0.9-lock') + + in_dir(@repository_dir) do + run_command("#{BRAID_BIN} upgrade-config") + assert_no_diff_in_braids(".braids.json", "expected.braids.json") + end + end + + it "from Braid 1.0.9 (.braids.json) with old-style lock with --dry-run should print info without performing the upgrade" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-1.0.9-lock') + + in_dir(@repository_dir) do + output = run_command("#{BRAID_BIN} upgrade-config --dry-run") + expect(output).to match(/Your configuration file will be upgraded from configuration version 0 to 1\./) + expect(output).not_to match(/The following breaking changes/) + # Instructions should not include --allow-breaking-changes if it isn't necessary. + expect(output).to match(/Run 'braid upgrade-config'/) + assert_no_diff_in_braids(".braids.json", "#{FIXTURE_PATH}/shiny-conf-1.0.9-lock/.braids.json") + end + end + + it "with breaking changes and --dry-run should print info without performing the upgrade" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-breaking-changes') + + in_dir(@repository_dir) do + output = run_command("#{BRAID_BIN} upgrade-config --dry-run") + expect(output).to match(/The following breaking changes/) + expect(output).to match(/Spoon-Knife.*Subversion/) + expect(output).to match(/skit1.*full-history/) + expect(output).to match(/Run 'braid upgrade-config --allow-breaking-changes'/) + assert_no_diff(".braids", "#{FIXTURE_PATH}/shiny-conf-breaking-changes/.braids") + expect(File.exists?(".braids.json")).to eq(false) + end + end + + it "with breaking changes should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-breaking-changes') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} upgrade-config") + expect(output).to match(/The following breaking changes/) + expect(output).to match(/Spoon-Knife.*Subversion/) + expect(output).to match(/skit1.*full-history/) + expect(output).to match(/You must pass --allow-breaking-changes/) + # `braid upgrade-config` should not have changed any files. + assert_no_diff(".braids", "#{FIXTURE_PATH}/shiny-conf-breaking-changes/.braids") + expect(File.exists?(".braids.json")).to eq(false) + end + end + + it "with breaking changes and --allow-breaking-changes should produce the expected configuration" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-breaking-changes') + + in_dir(@repository_dir) do + output = run_command("#{BRAID_BIN} upgrade-config --allow-breaking-changes") + expect(output).to match(/The following breaking changes/) + expect(output).to match(/Spoon-Knife.*Subversion/) + expect(output).to match(/skit1.*full-history/) + expect(output).to match(/Configuration upgrade complete\./) + expect(File.exists?(".braids")).to eq(false) + assert_no_diff(".braids.json", "expected.braids.json") + end + end + + it "from future config version should fail" do + @repository_dir = create_git_repo_from_fixture('shiny-conf-future') + + in_dir(@repository_dir) do + output = run_command_expect_failure("#{BRAID_BIN} upgrade-config") + expect(output).to match(/is too old to understand/) + end + end + + it "from current config version should do nothing and print expected message" do + # Generate a current-version configuration by adding a mirror. + @repository_dir = create_git_repo_from_fixture('shiny') + @vendor_repository_dir = create_git_repo_from_fixture('skit1') + + in_dir(@repository_dir) do + run_command("#{BRAID_BIN} add #{@vendor_repository_dir}") + output = run_command("#{BRAID_BIN} upgrade-config") + expect(output).to match(/already at the current configuration version/) + end + end + + it "with no Braid configuration should do nothing and print expected message" do + @repository_dir = create_git_repo_from_fixture('shiny') + + in_dir(@repository_dir) do + output = run_command("#{BRAID_BIN} upgrade-config") + expect(output).to match(/has no Braid configuration file/) + expect(File.exists?(".braids.json")).to eq(false) + end + end + + end + +end diff --git a/spec/integration/diff_spec.rb b/spec/integration/diff_spec.rb index dda2fe6..db3085f 100644 --- a/spec/integration/diff_spec.rb +++ b/spec/integration/diff_spec.rb @@ -112,8 +112,7 @@ run_command("git rev-parse --verify --quiet #{base_revision}^{commit}") run_command("git gc --quiet --prune=all") # Make sure it's gone now so we know we're actually testing Braid's fetch behavior. - `git rev-parse --verify --quiet #{base_revision}^{commit}` - raise "'git gc' did not delete the base revision from the repository." if $?.success? + run_command_expect_failure("git rev-parse --verify --quiet #{base_revision}^{commit}") diff = run_command("#{BRAID_BIN} diff skit1") diff --git a/spec/integration/integration_helper.rb b/spec/integration/integration_helper.rb index a8dd5d8..0625332 100644 --- a/spec/integration/integration_helper.rb +++ b/spec/integration/integration_helper.rb @@ -37,8 +37,8 @@ def with_editor_message(message = 'Make some changes') end end -def assert_no_diff(file1, file2) - run_command("diff -U 3 #{file1} #{file2}") +def assert_no_diff(file1, file2, extra_flags = "") + run_command("diff -U 3 #{extra_flags} #{file1} #{file2}") end def assert_commit_attribute(format_key, value, commit_index = 0) @@ -77,6 +77,12 @@ def run_command(command) output end +def run_command_expect_failure(command) + output = `#{command}` + raise "Expected command to fail but it succeeded: #{command}\nOutput: #{output}" if $?.success? + output +end + def update_dir_from_fixture(dir, fixture = dir) to_dir = File.join(TMP_PATH, dir) FileUtils.mkdir_p(to_dir) @@ -95,7 +101,7 @@ def create_git_repo_from_fixture(fixture_name, options = {}) run_command("git config --local user.email \"#{email}\"") run_command("git config --local user.name \"#{name}\"") run_command('git config --local commit.gpgsign false') - run_command('git add *') + run_command('git add .') run_command("git commit -m \"initial commit of #{fixture_name}\"") end