Commits

Anonymous committed 2bfd563

added more opts to bisect_bug

  • Participants
  • Parent commits e313fb7

Comments (0)

Files changed (15)

File TODO.markdown

-Here are the things we'd like to do. If you're looking to help out, read on.
-We have put them into several categories to trick\^H\^H\^H\^H\^Hencourage you into
-helping.
-
-**RULE NUMBER 1**: It's ok if you break amp. Go ahead and accidentally remove
-every file, commit, and then push it. We're not worried; we have immutable history
-and multiple copies of the repo and advanced knowledge of `amp/hg revert`. You
-can't hurt us.
-
-# Specifics
-
-## MultiIO
-MultiIO is due for a rewrite right about now. It just pretends n IOs are actually
-one. Think of it as a lazy `@io.map {|io| io.read }.join`. Read starting at the
-first stream, and only go to the next one if necessary.
-
-## Amp config
-Write now we save config information is ~/.hgrc, .hg/hgrc. We read from several
-other places as well. The system is kinda ugly and could use some TLC.
-
-### Issues to be worked around:
-- Different repos have their own incompatible config formats
-- Those formats are ugly and not nicely user-editable (and not code)
-- Each define their own config hierarchy 
-- Our hierarchy-traversal code is defined all over the codebase including the 
-  AmpConfig class, Dispatch, Amp::Support, and other places.
-
-## Remote Repositories
-They. All. Look. The. Same. At least as far as amp is concerned, at the moment.
-We need to fix this shiznizzle because this means you cannot use a foreign
-repository for anything other than Mercurial, really (based on how the files
-are loaded). Be creative. Be awesome. Be daring. Few in millions can speak like us.
-Show the world why you are a programmer. SOLVE THIS PROBLEM!
-
-## StagingArea => Stage
-Committing is a bitch and a half right now, through the API. First you specify
-what you want to have committed in the StagingArea, and then you have to interpret
-that "stage" (if you will) to determine what to commit. WTF?!?!?! Let's cut to the
-chase and try this: Stages (various StagingAreas) can be created based off of a
-bunch of files, and they just separate files into "added", "modified", "deleted",
-etc. (whatever commit wants them to be split into, a Stage will do it). Then, you
-can pass a stage into #commit and it will know everything. If no stage is passed,
-it will use the current stage (currently #staging_area). This way, things only
-have to be specified once.
-
-# Maintenance
-
-## Dependencies
-We need these down to zero. Currently we DO need YARD, but flay and flog? We
-haven't used these at all yet. Either remove these from the code (well, really
-the Rakefiles) or add a way to fail gracefully. We also now need minitest, etc.
-Development dependencies are ok, but dependencies for running? Kill them. Kill
-them aaaaaaalllllllllllll.
-
-## Multitude of Tests
-Tests. We need them. Moar and moar of them. We want every command to be tested
-(at least generally), although if every option were also tested that'd be
-superb.
-
-## Organization of Tests
-We currently have a gigantic test_functional.rb file that has most of the good
-tests. However, if there's a failure in the very beginning, the rest of the
-tests won't be run. It's true – some of the tests are dependent on each other,
-but perjaps there's a way to split them up into clusters that make sense and
-can be run independently.
-
-## Code Cleaning
-We have ugly code. We try to mark it with comments, we try to eliminate it in
-the first place, but seriously, when it comes to programming or doing some
-World Religions homework, I'm going to get the homework done first. And then
-some girl will have IMed me, and, well, you get the point. If you see ugly
-code, kill it. Hopefully it won't require any major architectural changes.
-
-## API Adherence
-Make the commands in the workflows stick to the API. If something doesn't adhere,
-change the API and change the command until it works. **NOTU BONE (Esperanto)**:
-Write a plethora of tests before changing a command, lest you break some
-little-known feature of the command and in its stead add a new "feature".
-
-# Expansion
-
-## Faster Bit Structs
-We experimented with using bitstructs to represent objects in files. Although
-this worked, it was MUCH slower than we could bare. We need a faster form of
-a bitstruct. A bitstruct is a standard C struct. If has a format, it has fields
-with names, and you can easily read and write them to and from files. Writing
-this alone is a task big enough for a young adult. We need these tested and
-benchmarked against not using bitstructs. Also, try to keep these pure ruby if
-you can.
-
-## Incorporating Bit Structs
-Take the bitstructs of the previous paragraph and incorporate them into
-everything. If you can, fix up the mercurial revlog API to make it suck a
-little less.
-
-## hg Extensions
-Start porting over the Mercurial extensions. 'Nuff said.
-
-## Expanding `amp serve`
-We'd like it to be more like BitBucket and GitHub. Go crazy. One thing you
-could do is implement other methods for storing users besides memory. There
-are incomplete frameworks for Sequel and DataMapper storage that need TLC.
-
-# Help
-
-## Pages of info
-We need to have specific pages explaining amp-specific features, and helping
-users get started using amp. Anything put into lib/amp/help/entries will be
-loaded with its filename as the help entry's name. So, if you create a file
-called "ampfiles.md", then "amp help ampfiles" will present the file you created.
-
-# Insects (low-low-low priority bugs)
-
-## Test Reloading
-Files get double-loaded when we run tests. Fix this. Kill the fucking insect.
-
-# Documentation
-
-## User guide
-We need a guide that will tell new users how to install and use amp. It should
-explain what to do if you get a bug. Add this into the help system so it can be
-CLI-accessible and browser-accessible. Add it to the man pages as well (see
-tasks/man.rake).
-
-## Inline documentation
-Go through to big ugly methods (or any method, no matter how dumb) and add
-inline comments explaining what the method does and HOW IT INTERACTS WITH
-THE REST OF THE SYSTEM. Comments should be formatted according to YARD
-documentation format (http://yardoc.org). Key questions to ask and answer:
-Who (uses this), What (is passed in), Why (this exists), and How (this
-interacts with the rest of the system).
-
-## Wiki
-We need to expand our BitBucket wiki so that it is more appeasing and useful.
-
-# Optimization
-
-## StagingArea#file_status
-This is called like a million times and it's unnecessary. We can use memoization
-to alleviate any pains. But be apprised: we have yet to feel any pains from it.
-
-# Random Complaints
-The standards for committing are ridiculous. They are so unfriendly to human and
-machine.
-
-The nomenclature for files and their statuses is absurd. It changes all over the
-place and is completely unstandardized.
-
-Comments don't explain why a method does something.
-
-Committing is fucking insane.

File bugs/22.rb

-require 'fileutils'
-
-Amp::Bug.new do |b|
-  dir  = "test_#{rand(1000)}"
-  repo = nil # scoping
-  
-  b.number 22
-  
-  # hint, because the functionality isn't there
-  b.desc   "`amp update` fails to switch between branches"
-  
-  b.setup do
-    # Create the repo
-    repo = Amp::Repositories::Mercurial::LocalRepository.new dir, true
-    
-    # Add a file (Ampfile is automatically created upon repo initialization)
-    repo.staging_area.add 'Ampfile'
-    
-    # Commit
-    repo.commit :modified => ['Ampfile'], :message => 'initial commit'
-    
-    # Create a new branch
-    repo.dirstate.branch = 'new_branch'
-    
-    # Save it
-    repo.commit :message => 'added a new branch'
-  end
-  
-  b.success do
-    begin
-      repo.update 'default'
-      repo[nil].branch == 'default' # repo[nil] is the working directory's changeset
-    rescue
-      false
-    end
-  end
-  
-  b.cleanup { FileUtils.rm_rf Dir['test_*'] } # destory all similar looking dirs
-end

File bugs/23.rb

-require 'fileutils'
-
-Amp::Bug.new do |b|
-  dir  = "test_#{rand(1000)}"
-  repo = nil # scoping
-  
-  b.number 23
-           
-  b.desc   "`amp update` fails to replay uncommitted changes"
-  
-  b.setup do
-    repo = Amp::Repositories::Mercurial::LocalRepository.new dir, true # Create the repo
-    repo.staging_area.add 'Ampfile'                                    # Add a file
-    repo.commit :modified => ['Ampfile'], :message => 'initial commit' # Commit
-    
-    repo.dirstate.branch = 'new_branch'                                # Create a new branch
-    repo.commit :message => 'added a new branch'                       # Save it
-    
-    repo.switch 'default'                                              # Switch back to default
-    File.open("#{dir}/unknown", 'w') {|f| f.write 'unknown' }          # Create an untracked file
-  end
-  
-  b.success do
-    repo.switch 'new_branch'                                           # Go back to new_branch
-    File.exist? "#{dir}/unknown"                                       # And the file should be there
-  end
-  
-  b.cleanup { FileUtils.rm_rf Dir['test_*'] } # destory all similar looking dirs
-end

File bugs/24.rb

-require 'fileutils'
-
-Amp::Bug.new do |b|
-  dir  = "test_#{rand(1000)}"
-  repo = nil # scoping
-  
-  b.number 23
-           
-  b.desc   "Unable to change branches"
-  
-  b.setup do
-    repo = Amp::Repositories::Mercurial::LocalRepository.new dir, true # Create the repo
-    repo.staging_area.add 'Ampfile'                                    # Add a file
-    repo.commit :modified => ['Ampfile'], :message => 'initial commit' # Commit
-    
-    repo.dirstate.branch = 'new_branch'                                # Create a new branch
-    repo.commit :message => 'added a new branch'                       # Save it
-    
-    puts repo[nil].branch
-  end
-  
-  b.success do
-    repo[nil].branch == 'new_branch'
-  end
-  
-  b.cleanup { FileUtils.rm_rf Dir['test_*'] } # destory all similar looking dirs
-end

File lib/amp/commands/commands/workflows/hg/bisect.rb

-#######################################################################
-#                  Licensing Information                              #
-#                                                                     #
-#  The following code is a derivative work of the code from the       #
-#  Mercurial project, which is licensed GPLv2. This code therefore    #
-#  is also licensed under the terms of the GNU Public License,        #
-#  verison 2.                                                         #
-#                                                                     #
-#  For information on the license of this code when distributed       #
-#  with and used in conjunction with the other modules in the         #
-#  Amp project, please see the root-level LICENSE file.               #
-#                                                                     #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                        #
-#                                                                     #
-#######################################################################
-
-command :bisect do |c|
-  c.workflow :hg
-  
-  c.desc "subdivision search of changesets"
-  c.help <<-EOS
-amp bisect [-gbsr] [-c CMD] [REV]
-  
-  This command helps to find changesets which introduce problems.
-  To use, mark the earliest changeset you know exhibits the problem
-  as bad, then mark the latest changeset which is free from the
-  problem as good. Bisect will update your working directory to a
-  revision for testing (unless the --noupdate option is specified).
-  Once you have performed tests, mark the working directory as bad
-  or good and bisect will either update to another candidate changeset
-  or announce that it has found the bad revision.
-  
-  As a shortcut, you can also use the revision argument to mark a
-  revision as good or bad without checking it out first.
-  
-  If you supply a command it will be used for automatic bisection. Its exit
-  status will be used as flag to mark revision as bad or good. In case exit
-  status is 0 the revision is marked as good, 125 - skipped, 127 (command not
-  found) - bisection will be aborted and any other status bigger than 0 will
-  mark revision as bad."
-  
-  Where options are:
-EOS
-  
-  c.opt :command, "The command to run to test", :short => '-c', :type => :string, :default => 'ruby'
-  c.opt :"dirty-room", "Eval the ruby code in -f in the context of this amp binary (faster than shelling out)", :short => '-d'
-  c.opt :file, "The file to run with --command (which defaults to ruby) for testing", :short => '-f', :type => :string
-  c.opt :"no-update", "Don't update the working directory during tests", :short => '-U'
-  c.opt :revs, "The revision range to search in", :short => '-r', :type => :string, :default => '0'
-  
-  c.before do |opts, args|
-    # Set the command to be the command and the file joined together in
-    # perfect harmony. If file isn't set, command will still work.
-    # If command isn't set, it defaults to 'ruby' up in the command parsing
-    # so actually it's always set unless there's a problem between the keyboard
-    # and chair. I'm sorry this isn't cross platform. Find room in your heart
-    # to forgive me.
-    opts[:command] = "#{opts[:command]} #{opts[:file]} 1>/dev/null 2>/dev/null"
-    
-    if opts[:"dirty-room"]
-      raise "The --dirty-room option needs --file as well" unless opts[:file]
-    end
-    
-    # If we have to preserve the working directory, then copy
-    # it to a super secret location and do the work there
-    if opts[:"no-update"]
-      require 'fileutils'
-      
-      opts[:testing_repo] = "../.amp_bisect_#{Time.now}"
-      FileUtils.cp_r repo.path, opts[:testing_repo]
-    end
-    
-    true
-  end
-  
-  c.after do |opts, args|
-    if opts[:"no-update"]
-      FileUtils.rm_rf opts[:testing_repo]
-    end
-  end
-  
-  c.on_run do |opts, args|
-    #################################
-    # VARIABLE PREP
-    #################################
-    # Set up some variables and make
-    # $display be set to false.
-    # Also set up what the proc is to
-    # test each revision. Assign a cute
-    # phrase to tell the user what's going
-    # on.
-    # 
-    
-    repo = opts[:repository]
-    old  = $display
-    $display = false # so revert won't be so chatty!
-    
-    # This is the sample to run. The proc needs to return true
-    # or false
-    if opts[:command]
-      using = "use `#{opts[:command].red}`"
-      run_sample = proc { system opts[:command] }
-    elsif opts[:"dirty-room"]
-      using = "evaluate #{opts[:file]} in this Ruby interpreter"
-      run_sample = proc { eval File.read(opts[:file]) }
-    else
-      raise "Must have the --command or --dirty-room option set!"
-    end
-    
-    last_good, last_bad = *c.parse_revision_range(opts[:revs])
-    last_bad ||= repo.size - 1
-    history = [last_bad]  # KILLME
-    
-    test_rev  = last_bad
-    is_good   = {} # {revision :: integer => good? :: boolean}
-    last_good.upto(last_bad) {|i| is_good[i] = nil }
-    
-    ########################################
-    # COMPLIMENT WHOEVER IS READING THE CODE
-    ########################################
-    
-    # Hey! That's a really nice shirt. Where'd you get it?
-    Amp::UI.say "Sweet computer, btw. I'm really digging this hardware.\n"
-    
-    
-    ########################################
-    # EXPLICITLY SAY WHAT WE'RE DOING
-    ########################################
-    Amp::UI.say <<-EOS
-OK! Terve! Today we're going to be bisecting your repository find a bug.
-Let's see... We're set to #{using} to do some bug hunting between revisions
-#{last_good.to_s.red} and #{last_bad.to_s.red}.
-
-Enough talk, let's go Orkin-Man on this bug!
-========
-EOS
-    
-    
-    #############################################
-    # BINARY SEARCH
-    #############################################
-    # Here's where we actually do the work. We're
-    # just going through in a standard binary
-    # search method. I haven't actually written
-    # a BS method in a long time so I don't know
-    # if this is official, but it works.
-    # 
-    
-    until (last_good - last_bad).abs < 1
-      repo.clean test_rev
-      
-      # keep the user updated
-      c.pretty_print is_good 
-      
-      # if the code sample works
-      if run_sample[]
-        is_good[test_rev] = true # then it's a success and mark it as such
-        break if test_rev == last_good
-        last_good = test_rev
-      else
-        is_good[test_rev] = false
-        last_bad = test_rev
-      end
-      
-      test_rev = (last_good + last_bad) / 2
-      history << test_rev
-    end
-    puts # clear the progress bar business
-    
-    ############################################
-    # CLEANING UP
-    ############################################
-    # Restore the working directory to its proper
-    # state and restore the $display variable.
-    # Report on the results of the binary search
-    # and say whether there is a bug, and if there
-    # is a bug, say where it starts.
-    # 
-    
-    repo.clean(repo.size - 1)
-    $display = old # and put things as they were
-    
-    if is_good[last_bad]
-      Amp::UI.say "The selected range of history passes the test. No bug found."
-    else
-      Amp::UI.say "Revision #{last_bad} has the bug!"
-    end
-  end
-  
-  def c.pretty_print(hash)
-    print("\b" * hash.size * 3)
-    print "\r"
-    print '['
-    hash.keys.sort[0..-2].each do |key|
-      case hash[key]
-      when true
-        print 'o, '
-      when false
-        print 'x, '
-      when nil
-        print '_, '
-      end
-    end
-    case hash[hash.keys.sort.last]
-    when true
-      print 'o'
-    when false
-      print 'x'
-    when nil
-      print '_'
-    end
-    print ']'
-  end
-end
-
-# Now for some helpers!
-module Kernel
-  def bisect_command(name, opts={})
-    command name.to_sym do |c|
-      
-      # set the default options as passed in
-      opts.each do |k, v|
-        c.default k, v
-      end
-      
-      yield self if block_given?
-    end
-  end
-end

File lib/amp/commands/commands/workflows/hg/branch.rb

-##################################################################
-#                  Licensing Information                         #
-#                                                                #
-#  The following code is licensed, as standalone code, under     #
-#  the Ruby License, unless otherwise directed within the code.  #
-#                                                                #
-#  For information on the license of this code when distributed  #
-#  with and used in conjunction with the other modules in the    #
-#  Amp project, please see the root-level LICENSE file.          #
-#                                                                #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                   #
-#                                                                #
-##################################################################
-
-command :branch do |c|
-  c.workflow :hg
-  
-  c.desc "Set/Show the current branch name"
-  c.opt :force, "Forces the branch-name change", :short => "-f"
-  c.opt :clean, "Resets the branch setting for this repository", :short => "-c"
-  
-  c.on_run do |opts, args|
-    repo = opts[:repository]
-    if opts[:clean]
-      _label = repo[nil].parents[0].branch # repo[nil] is the WorkingDirectoryChangeset
-      repo.dirstate.branch = _label
-      Amp::UI.status("Reset working directory to branch #{_label}")
-    elsif args.size > 0
-      _label = args.first
-      if !opts[:force] && repo.branch_tags.include?(_label)
-        if !repo.parents.map {|p| p.branch }.include?(_label)
-          raise abort("a branch of the same name already exists!"+
-                               " (use --force to override)")
-        end
-      end
-      repo.dirstate.branch = _label
-      Amp::UI.status("marked working directory as branch #{_label}")
-    else
-      Amp::UI.say repo.dirstate.branch
-    end
-  end
-end

File lib/amp/commands/commands/workflows/hg/update.rb

-##################################################################
-#                  Licensing Information                         #
-#                                                                #
-#  The following code is licensed, as standalone code, under     #
-#  the Ruby License, unless otherwise directed within the code.  #
-#                                                                #
-#  For information on the license of this code when distributed  #
-#  with and used in conjunction with the other modules in the    #
-#  Amp project, please see the root-level LICENSE file.          #
-#                                                                #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                   #
-#                                                                #
-##################################################################
-
-command :update do |c|
-  c.workflow :hg
-  
-  c.desc "Updates the current repository to the specified (or tip-most) revision"
-  c.opt :rev, "The revision # to use for updating.", { :short => "-r", :type => :string }
-  c.opt :node, "The node ID to use for updating.", { :short => "-n", :type => :string }
-  c.opt :clean, "Remove uncommitted changes from the working directory.", { :short => "-C" }
-  
-  c.on_run do |opts, args|
-    repo = opts[:repository]
-    
-    if opts[:rev] && opts[:node]
-      raise ArgumentError.new("Please only specify either --rev or --node.")
-    end
-    
-    rev = opts[:rev] || opts[:node] || args.shift
-    
-    # TODO: add --date option
-    if opts[:clean]
-      stats = repo.clean(rev)
-    else
-      stats = repo.update(rev)
-    end
-    
-    c.print_update_stats stats
-  end
-end

File lib/amp/extensions/bugs.rb

 
 command :bisect_bug do |c|
   c.desc "Easy shortcut for doing `amp bisect` while using the `bugs:test` command."
+  c.opt :"no-update", "Don't update the working directory during tests", :short => '-U'
+  c.opt :revs, "The revision range to search in", :short => '-r', :type => :string, :default => '0'
   
   c.on_run do |o, a|
     raise "Too many bisects going on! Only one argument accepted" if a.size > 1

File lib/amp/repository/abstract/abstract_staging_area.rb

-##################################################################
-#                  Licensing Information                         #
-#                                                                #
-#  The following code is licensed, as standalone code, under     #
-#  the Ruby License, unless otherwise directed within the code.  #
-#                                                                #
-#  For information on the license of this code when distributed  #
-#  with and used in conjunction with the other modules in the    #
-#  Amp project, please see the root-level LICENSE file.          #
-#                                                                #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                   #
-#                                                                #
-##################################################################
-
-module Amp
-  module Repositories
-    class AbstractStagingArea
-      include CommonStagingAreaMethods
-      
-      ##
-      # Marks a file to be added to the repository upon the next commit.
-      # 
-      # @param [[String]] filenames a list of files to add in the next commit
-      # @return [Boolean] true for success, false for failure
-      def add(*filenames)
-        raise NotImplementedError.new("add() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Marks a file to be removed from the repository upon the next commit. Last argument
-      # can be a hash, which can take an :unlink key, specifying whether the files should actually
-      # be removed or not.
-      # 
-      # @param [[String]] filenames a list of files to remove in the next commit
-      # @return [Boolean] true for success, false for failure
-      def remove(*filenames)
-        raise NotImplementedError.new("remove() must be implemented by subclasses of AbstractStagingArea.")
-      end
-        
-      ##
-      # Set +file+ as normal and clean. Un-removes any files marked as removed, and
-      # un-adds any files marked as added.
-      # 
-      # @param  [Array<String>] files the name of the files to mark as normal
-      # @return [Boolean] success marker
-      def normal(*files)
-        raise NotImplementedError.new("normal() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Mark the files as untracked.
-      # 
-      # @param  [Array<String>] files the name of the files to mark as untracked
-      # @return [Boolean] success marker
-      def forget(*files)
-        raise NotImplementedError.new("forget() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Marks a file to be copied from the +from+ location to the +to+ location
-      # in the next commit, while retaining history.
-      # 
-      # @param [String] from the source of the file copy
-      # @param [String] to the destination of the file copy
-      # @return [Boolean] true for success, false for failure
-      def copy(from, to)
-        raise NotImplementedError.new("copy() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Marks a file to be moved from the +from+ location to the +to+ location
-      # in the next commit, while retaining history.
-      # 
-      # @param [String] from the source of the file move
-      # @param [String] to the destination of the file move
-      # @return [Boolean] true for success, false for failure
-      def move(from, to)
-        raise NotImplementedError.new("move() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Marks a modified file to be included in the next commit.
-      # If your VCS does this implicitly, this should be defined as a no-op.
-      # 
-      # @param [[String]] filenames a list of files to include for committing
-      # @return [Boolean] true for success, false for failure
-      def include(*filenames)
-        raise NotImplementedError.new("include() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      alias_method :stage, :include
-      
-      ##
-      # Mark a modified file to not be included in the next commit.
-      # If your VCS does not include this idea because staging a file is implicit, this should
-      # be defined as a no-op.
-      # 
-      # @param [[String]] filenames a list of files to remove from the staging area for committing
-      # @return [Boolean] true for success, false for failure
-      def exclude(*filenames)
-        raise NotImplementedError.new("exclude() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      alias_method :unstage, :exclude
-      
-      ##
-      # Returns a Symbol.
-      # Possible results:
-      # :added (subset of :included)
-      # :removed
-      # :untracked
-      # :included
-      # :normal
-      #
-      def file_status(filename)
-        raise NotImplementedError.new("file_status() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # The directory used by the VCS to store magical information (.hg, .git, etc.).
-      #
-      # @api
-      # @return [String] relative to root
-      def vcs_dir
-        raise NotImplementedError.new("vcs_dir() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Saves the staging area's state.  Any added files, removed files, "normalized" files
-      # will have that status saved here.
-      def save
-        raise NotImplementedError.new("save() must be implemented by subclasses of AbstractStagingArea")
-      end
-      
-      ##
-      # Returns all files tracked by the repository *for the working directory* - not
-      # to be confused with the most recent changeset.
-      #
-      # @api
-      # @return [Array<String>] all files tracked by the repository at this moment in
-      #   time, including just-added files (for example) that haven't been committed yet.
-      def all_files
-        raise NotImplementedError.new("all_files() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # Returns whether the given directory is being ignored. Optional method - defaults to
-      # +false+ at all times.
-      #
-      # @api-optional
-      # @param [String] directory the directory to check against ignoring rules
-      # @return [Boolean] are we ignoring this directory?
-      def ignoring_directory?(directory)
-        false
-      end
-      
-      ##
-      # Returns whether the given file is being ignored. Optional method - defaults to
-      # +false+ at all times.
-      #
-      # @api-optional
-      # @param [String] file the file to check against ignoring rules
-      # @return [Boolean] are we ignoring this file?
-      def ignoring_file?(file)
-        false
-      end
-      
-      ##
-      # Does a detailed look at a file, to see if it is clean, modified, or needs to have its
-      # content checked precisely.
-      #
-      # Supplements the built-in #status command so that its output will be cleaner.
-      #
-      # Defaults to report files as normal - it cannot check if a file has been modified
-      # without this method being overridden.
-      #
-      # @api-optional
-      #
-      # @param [String] file the filename to look up
-      # @param [File::Stats] st the current results of File.lstat(file)
-      # @return [Symbol] a symbol representing the current file's status
-      def file_precise_status(file, st)
-        return :lookup
-      end
-      
-      ##
-      # Calculates the difference (in bytes) between a file and its last tracked state.
-      #
-      # Defaults to zero - in other words, it deactivates the delta feature.
-      #
-      # @api-optional
-      # @param [String] file the filename to look up
-      # @param [File::Stats] st the current results of File.lstat(file)
-      # @return [Fixnum] the number of bytes difference between the file and
-      #  its last tracked state.
-      def calculate_delta(file, st)
-        0
-      end
-      
-      ##
-      # All the files that are either added, modified, or merged.
-      # 
-      # @return [Array<String>] the files to be added in a commit
-      def modified
-        raise NotImplementedError.new("modified() must be implemented by subclasses of AbstractStagingArea.")
-      end
-      
-      ##
-      # All the files that are to be removed.
-      # 
-      # @return [Array<String>] the files to be removed in a commit
-      def removed
-        raise NotImplementedError.new("removed() must be implemented by subclasses of AbstractStagingArea.")
-      end
-    end
-  end
-end

File lib/amp/repository/mercurial/repo_format/branch_manager.rb

-##
-# Licensing history: originally directly ported from mercurial's source in
-# mid-Summer 2009. As a derivative work, the copyright is inherited from Mercurial's
-# GPLv2 license.
-#
-# 4/10/2010: Rewritten from scratch, using just example branchheads.cache
-# files in the amp repo, generated by Amp tools. As such all copyright goes to
-# Michael J. Edgar for all the code contained herein.
-#
-##################################################################
-#                  Licensing Information                         #
-#                                                                #
-#  The following code is licensed, as standalone code, under     #
-#  the Ruby License, unless otherwise directed within the code.  #
-#                                                                #
-#  For information on the license of this code when distributed  #
-#  with and used in conjunction with the other modules in the    #
-#  Amp project, please see the root-level LICENSE file.          #
-#                                                                #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                   #
-#                                                                #
-##################################################################
-
-module Amp
-  module Repositories
-    module Mercurial
-      ##
-      # = BranchManager
-      # Michael Scott for Amp.
-      #
-      # More seriously, this class handles reading/writing to the branch cache
-      # and figuring out what the head revisions are for each branch and such.
-      module BranchManager
-        # Going to need easy access to NULL_REV and NULL_ID
-        include Amp::Mercurial::RevlogSupport::Node
-        
-        DEFAULT_PARAMS = {:start => 0, :closed => false}
-        
-        ##
-        # Accesses specific branch head data from the repository. All arguments
-        # are optional.
-        #
-        # @param [Hash] opts the options for the branch head loading
-        # @option opts [Boolean] :branch (self[nil].branch) The branch to look up
-        # @option opts [Fixnum] :start (0) the revision to start frmo
-        # @option opts [Boolean] :closed (false) return closed branches if true
-        # @return [Array<String>] a list of node-ids that are the heads of branches
-        def branch_heads(opts = {})
-          opts = DEFAULT_PARAMS.merge(opts)
-          branch = opts[:branch] || default_branch_name
-          
-          all_heads = quickly_load_branch_heads(opts[:start])[branch]
-          all_heads.reverse!
-
-          if !opts[:closed]
-            # if a branch is closed, then the head changeset's extra field has "extra" => 1
-            all_heads.reject! {|node| self[node].extra["close"]}
-          end
-            
-          return all_heads
-        end
-        
-        ##
-        # Returns a dict where branch names map to a list of heads of
-        # the branch, open heads come before closed
-        #
-        # @return [Hash{String => String}] a hash, keyed by branch name. Each
-        #   key goes to the head of that branch
-        def branch_tags
-          quickly_load_branch_heads.inject({}) do |hash, (branch, list)| 
-            # Now we need to try to get an open head, but we'll take a closed
-            # one if we have to.
-
-            tipmost = list.reverse.find do |node| # find the first
-              !self[node].extra["close"] # that isn't closing a branch
-            end
-            tipmost ||= list.last # if they're all closing branches, pick the latest
-            hash[branch] = tipmost
-            hash
-          end
-        end
-        
-      private
-        
-        ##
-        # Returns whether the cache is valid or not. Will return false if
-        # there is no cache, as well.
-        #
-        # @return [Hash{String => Array<String>}] a mapping of branch names to a list
-        #   of heads of the branch with the same name
-        def cache_valid?
-          unless cached_heads
-            read_results = read_branch_cache
-            @cached_heads, @cached_tip_id = read_results[:heads], read_results[:tip_id]
-          end
-          return self["tip"].node_id == @cached_tip_id
-        end
-        
-        ##
-        # Returns the cached heads.
-        def cached_heads
-          @cached_heads ||= nil
-        end
-        
-        ##
-        # Caches the given mapping of branch names to lists of branch heads to file and in memory.
-        #
-        # @param [Hash{String => Array<String>}] mapping a mapping of branch names to a list
-        #   of heads of the branch with the same name
-        def cache(mapping)
-          write_branch_heads!(mapping)
-          @cached_heads = mapping
-          @cached_tip_id = self["tip"].node_id
-          return mapping
-        end
-        
-        ##
-        # Loads all the branch heads, and is where caching logic goes.
-        #
-        # @return [Hash{String => Array<String>}] a mapping of branch names to a list
-        #   of heads of the branch with the same name
-        def quickly_load_branch_heads(start = 0)
-          if cache_valid?
-            return cached_heads
-          else
-            return cache(scan_for_branch_heads(start, self.size - 1))
-          end
-        end
-        
-        ##
-        # Does a full scan of each repository node to find the heads of each branch.
-        #
-        # @param [Fixnum] from (0) the beginning node from which to search.
-        # @param [Fixnum] to (self.size - 1) the last node to look at in the search
-        # @return [Hash{String => Array<String>}] a mapping of branch names to a list
-        #   of heads of the branch with the same name
-        def scan_for_branch_heads(from = 0, to = self.size - 1)
-          Amp::UI.status "scanning for branch heads"
-          result = ArrayHash.new
-          from.upto(to) do |idx|
-            changeset = self[idx]
-            branch = changeset.branch
-            
-            # This node might be a head of its branch
-            result[branch] << changeset.node_id
-            
-            # The node's parents are definitely not heads. Remove them.
-            changeset.parents.each do |parent|
-              result[branch].delete parent.node_id
-            end
-          end
-          result
-        end
-        memoize_method :scan_for_branch_heads
-        
-        ##
-        # Reads all the branch heads, as well as the cached tip node and revision number.
-        # The results are returned as a hash.
-        #
-        # @return [Hash{Symbol => Object}] the results of reading in the data. The resulting
-        #   hash is keyed as follows:
-        #
-        #   :heads => [Hash{String => Array<String>}] a mapping of branch names to lists of
-        #       branch node IDs
-        #   :tip_id => [String] the string node ID of the tip revision when this cache was
-        #       stored
-        #   :tip_num => [Fixnum] the number of the tip revision when this cache was stored
-        def read_branch_cache
-          # first, get the lines
-          begin
-            lines = @hg_opener.read("branchheads.cache").split("\n")
-          rescue SystemCallError
-            return {:heads => nil, :tip_id => NULL_ID, :tip_num => NULL_REV}
-          end
-          
-          tip_id, tip_num = lines.first.split(" ", 2)
-          tip_num = tip_num.to_i
-          tip_id = tip_id.unhexlify
-          
-          heads = ArrayHash.new
-          lines[1..-1].each do |line|
-            next if line.empty?
-            node, branch = line.split(" ", 2)
-            heads[branch] << node.unhexlify
-          end
-          return {:heads => heads, :tip_id => tip_id, :tip_num => tip_num}
-        end
-        
-        ##
-        # Writes out *all* the branch heads.  This means we need to have a list for each
-        # branch with all its heads, even if there's just one.
-        #
-        # @param [Hash{String => Array<String>}] mapping a mapping of branch names to the
-        #   list of heads for that branch. Heads should be provided as node IDs in binary
-        #   form.
-        def write_branch_heads!(mapping)
-          @hg_opener.open("branchheads.cache","w") do |out|
-            out.puts "#{self["tip"].node_id.hexlify} #{self.size - 1}"
-            mapping.each do |branch, list|
-              list.each { |node| out.puts "#{node.hexlify} #{branch}" }
-            end
-          end
-        end
-        
-      end
-    end
-  end
-end

File lib/amp/repository/mercurial/repo_format/changeset.rb

-#######################################################################
-#                  Licensing Information                              #
-#                                                                     #
-#  The following code is a derivative work of the code from the       #
-#  Mercurial project, which is licensed GPLv2. This code therefore    #
-#  is also licensed under the terms of the GNU Public License,        #
-#  verison 2.                                                         #
-#                                                                     #
-#  For information on the license of this code when distributed       #
-#  with and used in conjunction with the other modules in the         #
-#  Amp project, please see the root-level LICENSE file.               #
-#                                                                     #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                        #
-#                                                                     #
-#######################################################################
-
-module Amp
-  module Mercurial
-    
-    ##
-    # A Changeset is a simple way of accessing the repository within a certain
-    # revision. For example, if the user specifies revision # 36, or revision
-    # 3adf21, then we can look those up, and work within the repository at the
-    # moment of that revision.
-    class Changeset < Amp::Repositories::AbstractChangeset
-      include Mercurial::RevlogSupport::Node
-      
-      attr_reader :repo
-      alias_method :repository, :repo
-      
-      ##
-      # Initializes a new changeset. We need a repository to work with, and also
-      # a change_id. this change_id could be a revision index or a node_id for
-      # the revision.
-      #
-      # @param [Repository] repo a repository to work with.
-      # @param [Integer, String] change_id an ID or index to lookup to find this
-      #   changeset.
-      #
-      def initialize(repo, change_id='')
-        change_id = '.' if change_id == ''
-        @repo = repo
-        if change_id.kind_of? Integer
-          @revision = change_id
-          @node_id  = @repo.changelog.node_id_for_index change_id
-        else
-          @node_id = @repo.lookup change_id
-          @revision = @repo.changelog.rev @node_id
-        end
-        @parents = nil
-      end
-      
-      ##
-      # Converts the revision to a number
-      def to_i; @revision; end
-      
-      ##
-      # Converts the revision to an easy-to-digest string
-      def to_s(opts = {})
-        if opts[:template]
-          to_templated_s(opts)
-        else
-          @node_id[0..5].hexlify
-        end
-      end
-      
-      ##
-      # Commits the given changeset to the repository.
-      # 
-      #  commit_changeset:
-      #    foreach file in commit:
-      #        commit_file file
-      #    end
-      #    add_manifest_entry
-      #    add_changelog_entry
-      # Is this changeset a working changeset?
-      #
-      # @param changeset the changeset to commit. Could be working dir, for
-      #   example.
-      # @param opts the options for committing the changeset.
-      # @option opts [Boolean] :force (false) force the commit, even though
-      #   nothing has changed.
-      # @option opts [Boolean] :force_editor (false) force the user to open
-      #   their editor, even though they provided a message already
-      # @option opts [Boolean] :empty_ok (false) is it ok if they have no
-      #   description of the commit?
-      # @option opts [Boolean] :use_dirstate (true) use the DirState for this
-      #   commit? Used if you're committing the working directory (typical)
-      # @option opts [Boolean] :update_dirstate (true) should we update the
-      #   DirState after the commit? Used if you're committing the working
-      #   directory.
-      # @return [String] the digest referring to this entry in the revlog
-      def commit(opts = {:use_dirstate => true, :update_dirstate => true})
-        valid = false # don't update the DirState if this is set!
-        
-        commit = ((modified || []) + (added || [])).sort
-        remove = removed
-        xtra = extra.dup
-        branchname = xtra["branch"]
-        text = description
-        
-        p1, p2 = parents.map {|p| p.node }
-        c1 = repo.changelog.read(p1) # 1 parent's changeset as an array
-        c2 = repo.changelog.read(p2) # 2nd parent's changeset as an array
-        m1 = repo.manifest.read(c1[0]).dup # 1st parent's manifest
-        m2 = repo.manifest.read(c2[0])     # 2nd parent's manifest
-        
-        if opts[:use_dirstate]
-          oldname = c1[5]["branch"]
-          tests = [ commit.empty?, remove.empty?, !opts[:force],
-                    p2 == NULL_ID, branchname == oldname ]
-          if tests.all?
-            UI::status "nothing changed"
-            return nil
-          end
-        end
-        
-        xp1 = p1.hexlify
-        xp2 = p2 == NULL_ID ? "" : p2.hexlify
-        
-        Hook.run_hook :pre_commit
-        journal = Amp::Mercurial::Journal.new(:opener => repo.store_opener)
-  
-        fresh    = {} # new = reserved haha i don't know why someone wrote "haha"
-        changed  = []
-        link_rev = repo.size
-        
-        (commit + (remove || [])).each {|file| UI::status file }
-        
-        # foreach file in commit:
-        #     commit_file file
-        # end
-        commit.each do |file|
-          versioned_file = self[file]
-          fresh[file]    = versioned_file.commit :manifests     => [m1, m2],
-                                                 :link_revision => link_rev,
-                                                 :journal       => journal ,
-                                                 :changed       => changed
-          
-          new_flags = versioned_file.flags
-          
-          # TODO
-          # Clean this shit up
-          if [ changed.empty? || changed.last != file, 
-               m2[file] != fresh[file]
-             ].all?
-            changed << file if m1.flags[file] != new_flags
-          end
-          m1.flags[file] = new_flags
-          
-          repo.staging_area.normal file if opts[:use_dirstate]
-        end
-        
-        #    add_manifest_entry
-        man_entry, updated, added = *add_manifest_entry(:manifests  => [m1, m2],
-                                                        :changesets => [c1, c2],
-                                                        :journal    => journal ,
-                                                        :link_rev   => link_rev,
-                                                        :fresh      => fresh   ,
-                                                        :remove     => remove  ,
-                                                        :changed    => changed )
-
-        #    get_commit_text
-        text = get_commit_text text, :added   => added,   :updated => updated,
-                                     :removed => removed, :user    => user   ,
-                                     :empty_ok     => opts[:empty_ok]        ,
-                                     :use_dirstate => opts[:use_dirstate]
-        
-        # atomically write to the changelog
-        #    add_changelog_entry
-        # for the unenlightened, rents = 'rents = parents
-        new_rents = add_changelog_entry :manifest_entry => man_entry,
-                                        :files   => (changed + removed),
-                                        :text    => text,
-                                        :journal => journal,
-                                        :parents => [p1, p2],
-                                        :user    => user,
-                                        :date    => date,
-                                        :extra   => xtra
-      
-        
-        # Write the dirstate if it needs to be updated
-        # basically just bring it up to speed
-        if opts[:use_dirstate] || opts[:update_dirstate]
-          repo.dirstate.parents = new_rents
-          removed.each {|f| repo.dirstate.forget(f) } if opts[:use_dirstate]
-          repo.staging_area.save
-        end
-      
-        # The journal and dirstates are awesome. Leave them be.
-        valid = true
-        journal.close
-        
-        # if an error and we've gotten this far, then the journal is complete
-        # and it deserves to stay (if an error is thrown and journal isn't nil,
-        # the rescue will destroy it)
-        journal = nil
-        
-        # Run any hooks
-        Hook.run_hook :post_commit, :added => added, :modified => updated, :removed => removed, 
-                                    :user  => user,  :date     => date,    :text    => text,
-                                    :revision => repo.changelog.index_size
-        return new_rents
-      rescue StandardError => e
-        if !valid
-          repo.dirstate.invalidate!
-        end
-        if e.kind_of?(AbortError)
-          UI::warn "Abort: #{e}"
-        else
-          UI::warn "Got exception while committing. #{e}"
-          UI::warn e.backtrace.join("\n")
-        end
-        
-        # the journal is a vestigial and incomplete file.
-        # destroyzzzzzzzzzzz
-        journal.delete if journal
-      end
-      
-      ##
-      # Add an entry to the changelog (the final receipt of the commit).
-      # 
-      # @param [Hash] opts
-      # @return [String] the changelog id as to where the revision is in
-      #   the changelog
-      def add_changelog_entry(opts={})
-        repo.changelog.delay_update 
-        new_parents = repo.changelog.add opts[:manifest_entry],
-                                         opts[:files],
-                                         opts[:text],
-                                         opts[:journal],
-                                         opts[:parents][0],
-                                         opts[:parents][1],
-                                         opts[:user],
-                                         opts[:date],
-                                         opts[:extra]
-  
-        repo.changelog.write_pending
-        repo.changelog.finalize opts[:journal]
-        new_parents
-      end
-      
-      ##
-      # Get the commit text. Ask for it if none is given.
-      # 
-      # @param [String, NilClass] text (optional) the commit message
-      # @param [Hash] opts
-      def get_commit_text(text=nil, opts={})
-        user = opts.delete :user
-        
-        unless opts[:empty_ok] || (text && !text.empty?)
-          edit_text = to_templated_s :added   => added,   :updated       => modified,
-                                     :removed => removed, :template_type => :commit
-          text = UI::edit edit_text, user
-        end
-        
-        lines = text.rstrip.split("\n").map {|r| r.rstrip }.reject {|l| l.empty? }
-        raise abort("empty commit message") if lines.empty? && opts[:use_dirstate]
-        lines.join("\n")
-      end
-      
-      def add_manifest_entry(opts={})
-        # changed, m1, m2, c1, c2, fresh, remove, journal, link_rev
-        updated, added = [], []
-        
-        fresh   = opts[:fresh]
-        remove  = opts[:remove]
-        changed = opts[:changed]
-        
-        changesets = opts[:changesets]
-        manifests  = opts[:manifests]
-        
-        changed.sort.each do |file|
-          if manifests[0][file] || manifests[1][file]
-            updated << file
-          else
-            added << file
-          end
-        end
-        
-        manifests[0].merge! fresh
-        
-        remove.sort!
-        remove.reject! {|f| not manifests[0][f] }
-        remove.each {|f| manifests[0].delete f }
-        
-        UI::debug "before adding manifest entry"
-        
-        # sorry for making this destructive
-        # but it's clean and memory efficient
-        # GHC's GC goes like 3 times per second, so STFU
-        # I don't have that kind of luxury
-        fresh.replace fresh.inject([]) {|a, (k, v)| v ? a << k : a }
-        man_entry = repo.manifest.add manifests[0], opts[:journal],
-                                      opts[:link_rev], changesets[0][0], changesets[1][0], [fresh, remove]
-        [man_entry, updated, added]
-      end
-      
-      ##
-      # @return [Boolean] is the changeset representing the working directory?
-      def working?
-        false
-      end
-      
-      ##
-      # Gives an easier way to digest this changeset while reminding us it's a
-      # changeset
-      def inspect
-        "#<Changeset #{to_s}>"
-      end
-      
-      ##
-      # Hash function for putting these bad boys in hashes
-      # 
-      # @return [Integer] a hash value.
-      def hash
-        return @revision.hash if @revision
-        return object_id
-      end
-      
-      ##
-      # Compares 2 changesets so we can sort them and whatnot
-      # 
-      # @param [Changeset] other a changeset we will compare against
-      # @return [Integer] -1, 0, or 1. Typical comparison.
-      def <=>(other)
-        return 0 if @revision.nil? || other.revision.nil?
-        @revision <=> other.revision
-      end
-      
-      ##
-      # Are these two changesets equivalent?
-      # 
-      # @param  [Changeset] other a changeset we will compare against
-      # @return [Boolean] whether they're equivalent
-      def ==(other)
-        (self <=> other) == 0
-      end
-      
-      ##
-      # Are we a null revision?
-      # @return [Boolean] null?
-      def nil?
-       @revision != NULL_REV
-      end
-      alias_method :null?, :nil?
-      
-      # Gets the raw changeset data for this revision. This includes
-      # the user who committed it, the description of the commit, and so on.
-      # Returns this: [manifest, user, [time, timezone], files, desc, extra]
-      def raw_changeset
-        @repo.changelog.read(@node_id)
-      end
-      
-      ##
-      # Returns the {ManifestEntry} for this revision. This will give
-      # us info on any file we want, including flags such as executable
-      # or if it's a link. Sizes and so on are also included.
-      #
-      # @return [ManifestEntry] the manifest at this point in time
-      def manifest_entry
-        @manifest_entry ||= @repo.manifest.read(raw_changeset.manifest_node)
-      end
-      
-      ##
-      # Provides access to all the tracked files in the changeset. Needed
-      # for API compatibility.
-      #
-      # @return [Array<String>] all the files tracked in this changeset.
-      def all_files
-        return manifest_entry.files
-      end
-      
-      ##
-      # Returns the change in the manifest at this revision. I don't entirely
-      # know what this is yet.
-      def manifest_delta
-        @manifest_entry_delta ||= @repo.manifest.read_delta(raw_changeset.manifest_node)
-      end
-      
-      ##
-      # Returns the parents of this changeset as {Changeset}s.
-      #
-      # @return [[Changeset]] the parents of this changeset.
-      def parents
-        return @parents if @parents
-        
-        p = @repo.changelog.parent_indices_for_index @revision
-        p = [p[0]] if p[1] == NULL_REV
-        
-        @parents = p.map {|x| Changeset.new(@repo, x) }
-      end
-      
-      ##
-      # Returns the children of this changeset as {Changeset}s.
-      #
-      # @return [Array<Changeset>] the children of this changeset.
-      def children
-        @repo.changelog.children(node_id).map do |node|
-          Changeset.new(@repo, node)
-        end
-      end
-      
-      ##
-      # Iterates over each entry in the manifest entry.
-      # 
-      # @return [Changeset] self, because that's how #each works
-      def each(&block)
-        manifest_entry.sort.each(&block)
-        self
-      end
-      
-      ##
-      # Checks whether this changeset included a given file or not.
-      # 
-      # @param [String] file the file to lookup
-      # @return [Boolean] whether the file is in this changeset's manifest
-      def include?(file)
-        manifest_entry[file] != nil
-      end 
-      
-      ##
-      # Gets the file with the given name, as a {VersionedFile}.
-      # @param file the path to the file to retrieve
-      # @return [VersionedFile] the file at this revision
-      #
-      def [](file)
-        get_file(file)
-      end
-      
-      ##
-      # Returns the file's info, namely it's node_id and flags it may
-      # have at this point in time, such as "x" for executable.
-      # 
-      # @param path the path to the file
-      # @return [[String, String]] the [node_id, flags] pair for this file
-      def file_info(path)
-        if manifest_entry # have we loaded our manifest yet? if so, use that sucker
-          result = [manifest_entry[path], manifest_entry.flags[path]]
-          if result[0].nil?
-            return [NULL_ID, '']
-          else
-            return result
-          end
-        end
-        if manifest_delta || files[path] # check if it's in the delta... i dunno
-          if manifest_delta[path]
-            return [manifest_delta[path], manifest_delta.flags[path]]
-          end
-        end
-        # Give us, just look it up the long way in the manifest. not fun. slow.
-        node, flag = @repo.manifest.find(raw_changeset[0], path)
-        if node.nil?
-          return [NULL_ID, '']
-        end
-        return [node, flag]
-      end
-      
-      ##
-      # Gets the flags for the file at the given path at this revision.
-      # @param path the path to the file in question
-      # @return [String] the flags for the file, such as "x", "l", or "".
-      #
-      def flags(path)
-        info = file_info(path)[1]
-        return "" if info.nil?
-        info
-      end
-      
-      ##
-      # Gets the node_id in the manifest_entry for the file at this path, for this
-      # specific revision.
-      # 
-      # @param path the path to the file
-      # @return [String] the node's ID in the manifest_entry, which we'll use every
-      #   where we need a node_id.
-      def file_node(path)
-        file_info(path).first[0..19]
-      end
-      
-      ##
-      # Creates a versioned file for the file at the given path, for the frame
-      # of reference of this revision.
-      # @param path the path to the file
-      # @param [String] file_id the node_id, to save us some computation
-      # @param [FileLog] file_log the file_log to use, again to save us computation
-      # @return [VersionedFile] the file at this revision.
-      #
-      def get_file(path, file_id = nil, file_log = nil)
-        file_id = file_node(path) if file_id.nil?
-        VersionedFile.new(@repo, path, :file_id => file_id, :changeset => self,
-                                       :file_log => file_log)
-      end
-      #accessors
-      # revision index
-      def revision; @revision; end
-      alias_method :rev, :revision
-      # node_id
-      def node_id; @node_id; end
-      # @see node_id
-      alias_method :node, :node_id
-      # our node_id in sexy hexy
-      def hex; @node_id.hexlify; end
-      # the user who committed me!
-      def user; raw_changeset.user; end
-      # the date i was committed!
-      def date; raw_changeset.time; end
-      def easy_date; Time.at(raw_changeset.time.first); end
-      # the files affected in this commit!
-      def altered_files; raw_changeset.files; end
-      # pre-API compatibility
-      alias_method :files, :altered_files
-      
-      # the message with this commit
-      def description; raw_changeset.description; end
-      # What branch i was committed onto
-      def branch 
-        extra["branch"] 
-      end
-      # Any extra stuff I've got in me
-      def extra; raw_changeset.extra; end
-      # tags
-      def tags; @repo.tags_for_node node; end
-      
-      def ancestor(other_changeset)
-        node = @repo.changelog.ancestor(self.node, other_changeset.node)
-        return Changeset.new(@repo, node)
-      end
-      
-      def ancestors
-        results = []
-        @repo.changelog.ancestors(revision)
-      end
-    end
-    
-    ##
-    # This is a special changeset that specifically works within the
-    # working directory. We sort of have to combine the old revision
-    # logs with the fact that files might be changed, and not in the
-    # revision logs! oh, mercy!
-    class WorkingDirectoryChangeset < Changeset
-      
-      def initialize(repo, opts={:text => ""})
-        @repo = repo
-        @revision = nil
-        @parents = nil
-        @node_id  = nil
-        @text = opts[:text]
-        require 'time' if opts[:date].kind_of?(String)
-        @date = opts[:date].kind_of?(String) ? Time.parse(opts[:date]) : opts[:date]
-        @user = opts[:user] if opts[:user]
-        @parents = opts[:parents].map {|p| Changeset.new(@repo, p)} if opts[:parents]
-        @status = opts[:changes] if opts[:changes]
-        @manifest = nil
-        @manifest_entry = nil
-        @extra = opts[:extra] ? opts[:extra].dup : {}
-        unless @extra["branch"]
-          branch = @repo.dirstate.branch
-          # encoding - to UTF-8
-          @extra["branch"] = branch
-        end
-        @extra["branch"] = "default" if @extra["branch"] && @extra["branch"].empty?
-        
-      end
-      
-      ##
-      # Is this changeset a working changeset?
-      #
-      # @return [Boolean] is the changeset representing the working directory?
-      def working?
-        true
-      end
-      
-      ##
-      # Converts to a string.
-      # I'm my first parent, plus a little extra.
-      # "I am my own grandpa"
-      # 
-      # @return [String]
-      def to_s
-        if (altered_files + status[:deleted]).any?
-        then parents.first.to_s + "+"
-        else parents.first.to_s
-        end
-      end
-      
-      
-      ##
-      # Do I include a given file? (not sure this is ever used yet)
-      def include?(key)
-        status = @repo.staging_area.file_status(key)
-        ![:unknown, :removed].include?(status)
-      end
-      
-      def all_files
-        repo.staging_area.all_files
-      end
-      
-      ##
-      # Am I nil? never!
-      def nil?; false; end
-      
-      ##
-      # What is the status of the working directory? This little
-      # method hides quite a bit of work!
-      def status
-        @status ||= @repo.status(:unknown => true)
-      end
-      
-      ##
-      # Who is the user working on me?
-      def user 
-        @user ||= @repo.config.username
-      end
-      
-      ##
-      # Well, I guess the working directory's date is... right now!
-      def date
-        @date ||= Time.new
-      end
-      
-      ##
-      # Who is the working directory's father? Is it Chef? Mr. Garrison?
-      # the 1989 denver broncos?
-      #
-      # hahaha mike that's hilarious
-      def parents
-        @parents ||= begin
-                       p = @repo.dirstate.parents
-                       p = [p[0]] if p[1] == NULL_ID
-                       p.map {|x| Changeset.new(@repo, x) }
-                     end
-      end
-      
-      ##
-      # OK, so we've got the last revision's manifest entry, that part's simple and makes sense.
-      # except now, we need to get the status of the working directory, and
-      # add in all the other files, because they're in the "manifest entry" by being
-      # in existence. Oh, and we need to remove any files from the parent's
-      # manifest entry that don't exist anymore. Make sense?
-      def manifest_entry
-        return @manifest_entry if @manifest_entry
-        
-        # Start off with the last revision's manifest_entry, that's safe.
-        man = parents()[0].manifest_entry.dup
-        # Any copied files since the last revision?
-        copied = @repo.dirstate.copy_map
-        # Any modified, added, etc files since the last revision?
-        modified, added, removed  = status[:modified], status[:added], status[:removed]
-        deleted, unknown          = status[:deleted], status[:unknown]
-        # Merge these discoveries in!
-        {:a => added, :m => modified, :u => unknown}.each do |k, list|
-          list.each do |file|
-            copy_name = (copied[file] || file)
-            man[file] = (man.flags[copy_name] || NULL_ID) + k.to_s
-            man.flags[file] = @repo.dirstate.flags(file)
-          end
-        end
-        
-        # Delete files from the real manifest entry that don't exist.
-        (deleted + removed).each do |file|
-          man.delete file if man[file]
-        end
-        
-        @manifest_entry = man
-      end
-      
-      ##
-      # Returns a {VersionedWorkingFile} to represent the file at the given
-      # point in time. It represents a file in the working directory, which
-      # obvious don't read from the history, but from the actual file in
-      # question.
-      # 
-      # @param path the path to the file
-      # @param file_log the log for the file to save some computation
-      # @return [Amp::VersionedWorkingFile] the file object we can work with
-      def get_file(path, file_log=nil)
-        VersionedWorkingFile.new(@repo, path, :working_changeset => self, 
-                                              :file_log => file_log)
-      end
-      
-      ##
-      # Gets the flags for the file at current state in time
-      # 
-      # @param [String] path the path to the file
-      # @return [String] the flags, such as "x", "l", or ""
-      def flags(path)
-        if @manifest_entry ||= nil
-          return manifest_entry.flags[path] || ""
-        end
-        pnode = parents[0].raw_changeset[0]
-    
-        orig = @repo.dirstate.copy_map[path] || path
-        node, flag = @repo.manifest.find(pnode, orig)
-        return @repo.dirstate.flags(@repo.working_join(path))
-      end
-      
-      def useful_parents
-        parents = @parents
-        if parents[1].nil?
-          if parents[0].revision >= @repo.size - 1
-            parents = []
-          else
-            parents = [parents[0]]
-          end
-        end
-        parents
-      end
-      
-      ##
-      # What's the hex hash of this revision? Just return nil, because we
-      # haven't been committed yet.
-      def hex
-        nil # noop
-      end
-      
-      ##
-      # Recursively walk the directory tree, getting all files that +match+ says
-      # are good.
-      # 
-      # @param [Amp::Match] match how to select the files in the tree
-      # @param [Boolean] check_ignored (false) should we check for ignored files?
-      # @return [Array<String>] an array of filenames in the tree that match +match+
-      def walk(match, check_ignored = false)
-        tree = @repo.staging_area.walk true, check_ignored, match
-        tree.keys.sort
-      end
-      
-      # If there's a description, ok then
-      def description; @text; end
-      # Files affected in this transaction: modified, added, removed.
-      def altered_files; (modified + added + removed).sort; end
-      # What files have changed?
-      def modified; status[:modified] || []; end
-      # What files have we added?
-      def added; status[:added] || []; end
-      # What files have been removed?
-      def removed; status[:removed] || []; end
-      # What files have been deleted (but not officially)?
-      def deleted; status[:deleted] || []; end
-      # What files are hanging out, but untracked?
-      def unknown; status[:unknown] || []; end
-      # What files are pristine since the last revision?
-      def clean; status[:normal] || []; end
-      # What branch are we in?
-      def branch; @extra['branch']; end
-      # Set the branch
-      def branch=(name); @extra['branch'] = name; end
-      # Any other extra data? i'd like to hear it
-      def extra; @extra; end
-      # No children. Returns the empty array.
-      def children; []; end
-      # The date, nicely handled
-      def easy_date; date; end
-      # The tags
-      def tags; self.parents.first.tags; end
-    end
-  end
-end

File lib/amp/repository/mercurial/repo_format/staging_area.rb

-#######################################################################
-#                  Licensing Information                              #
-#                                                                     #
-#  The following code is a derivative work of the code from the       #
-#  Mercurial project, which is licensed GPLv2. This code therefore    #
-#  is also licensed under the terms of the GNU Public License,        #
-#  verison 2.                                                         #
-#                                                                     #
-#  For information on the license of this code when distributed       #
-#  with and used in conjunction with the other modules in the         #
-#  Amp project, please see the root-level LICENSE file.               #
-#                                                                     #
-#  © Michael J. Edgar and Ari Brown, 2009-2010                        #
-#                                                                     #
-#######################################################################
-
-module Amp
-  module Repositories
-    module Mercurial
-      class StagingArea < AbstractStagingArea
-        
-        attr_reader :dirstate
-        attr_reader :repo
-        alias_method :repository, :repo
-        
-        def initialize(repo)
-          @ignore_all = nil
-          @repo = repo
-          @check_exec = false
-        end
-        
-        ######### API Methods #################################################
-        
-        ##
-        # All the files that are either added, modified, or merged.
-        # 
-        # @return [Array<String>] the files to be added in a commit
-        def modified
-          dirstate.files.select {|f| f.merged? || f.modified? || f.added? }
-        end
-        
-        ##
-        # All the files that are to be removed.
-        # 
-        # @return [Array<String>] the files to be removed in a commit
-        def removed
-          dirstate.files.select {|f| f.removed? }
-        end
-        
-        ##
-        # The directory used by the VCS to store magical information (.hg, .git, etc.).
-        #
-        # @api
-        # @return [String] relative to root
-        def vcs_dir
-          '.hg'
-        end
-        
-        ##
-        # Adds a list of file paths to the repository for the next commit.
-        # 
-        # @api
-        # @param [String, Array<String>] paths the paths of the files we need to
-        #   add to the next commit
-        # @return [Array<String>] which files WEREN'T added
-        def add(*files)
-          rejected = []
-          files.flatten!
-          
-          repo.lock_working do
-            files.each do |file|
-              path = repo.working_join file
-              stat = File.exist?(path) && File.lstat(path)
-              
-              if !stat
-                UI.warn "#{file} does not exist!"
-                rejected << file
-              elsif File.ftype(path) != 'file' && File.ftype(path) != 'link'
-                UI.warn "#{file} not added: only files and symlinks supported. Type is #{File.ftype path}"
-                rejected << path
-              else
-                if stat.size > 10.mb
-                  UI.warn "#{file}: files over 10MB may cause memory and performance problems\n" +
-                              "(use 'amp revert #{file}' to unadd the file)\n"
-                end
-                dirstate.add file
-              end
-            end
-          end
-          rejected
-        end
-        
-        ##
-        # Removes the file (or files) from the repository. Marks them as removed
-        # in the DirState, and if the :unlink option is provided, the files are
-        # deleted from the filesystem.
-        #
-        # @api
-        # @param list the list of files. Could also just be 1 file as a string.
-        #   should be paths.
-        # @param opts the options for this removal.
-        # @option opts [Boolean] :unlink (false) whether or not to delete the
-        #   files from the filesystem after marking them as removed from the
-        #   DirState.
-        # @return [Boolean] success?
-        def remove(*args)
-          list = args.last.is_a?(Hash) ? args[0..-2].flatten : args[0..-1].flatten
-          opts = args.last.is_a?(Hash) ? args.last : {}
-          # Should we delete the filez?
-          if opts[:unlink]
-              FileUtils.safe_unlink list.map {|f| repo.working_join(f)}
-          end
-          success = true
-          repo.lock_working do
-            # Save ourselves a dirstate write
-            list.each do |f|
-              if opts[:unlink] && File.exists?(repo.working_join(f))
-                # Uh, why is the file still there? Don't remove it from the dirstate
-                UI.warn("#{f} still exists!")
-                success = false # no success
-              else
-                dirstate.remove f
-              end
-            end
-          end
-          
-          success