rubycut-babushka 0.10.8 → 0.15.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +17 -15
  3. data/README.markdown +163 -41
  4. data/Rakefile +1 -1
  5. data/bin/babushka +1 -1
  6. data/deps/apt.rb +44 -0
  7. data/deps/babushka.rb +54 -42
  8. data/deps/deprecated.rb +16 -0
  9. data/deps/dev.rb +28 -3
  10. data/deps/git.rb +27 -12
  11. data/deps/homebrew.rb +2 -2
  12. data/deps/packages.rb +14 -15
  13. data/deps/pkg_managers.rb +21 -75
  14. data/deps/ruby.rb +5 -19
  15. data/deps/rubygems.rb +3 -3
  16. data/deps/system.rb +2 -2
  17. data/deps/templates/app.rb +60 -41
  18. data/deps/templates/bin.rb +16 -0
  19. data/deps/templates/installer.rb +9 -9
  20. data/deps/templates/lib.rb +17 -0
  21. data/deps/templates/managed.rb +1 -38
  22. data/deps/templates/src.rb +16 -8
  23. data/deps/templates/task.rb +11 -0
  24. data/deps/templates/tmbundle.rb +16 -2
  25. data/lib/babushka.rb +2 -3
  26. data/lib/babushka/accepts_block_for.rb +5 -3
  27. data/lib/babushka/asset.rb +172 -0
  28. data/lib/babushka/base.rb +37 -8
  29. data/lib/babushka/bug_reporter.rb +6 -6
  30. data/lib/babushka/cmdline.rb +11 -10
  31. data/lib/babushka/cmdline/handler.rb +7 -3
  32. data/lib/babushka/cmdline/helpers.rb +15 -23
  33. data/lib/babushka/cmdline/parser.rb +1 -1
  34. data/lib/babushka/core_patches/object.rb +1 -1
  35. data/lib/babushka/core_patches/string.rb +8 -3
  36. data/lib/babushka/current_ruby.rb +44 -0
  37. data/lib/babushka/dep.rb +111 -185
  38. data/lib/babushka/dep_context.rb +8 -3
  39. data/lib/babushka/dep_definer.rb +45 -15
  40. data/lib/babushka/dep_pool.rb +5 -8
  41. data/lib/babushka/{meta_dep.rb → dep_template.rb} +21 -2
  42. data/lib/babushka/dsl.rb +3 -0
  43. data/lib/babushka/git_repo.rb +143 -49
  44. data/lib/babushka/helpers/git_helpers.rb +7 -6
  45. data/lib/babushka/helpers/log_helpers.rb +51 -13
  46. data/lib/babushka/helpers/path_helpers.rb +5 -7
  47. data/lib/babushka/helpers/run_helpers.rb +15 -55
  48. data/lib/babushka/helpers/shell_helpers.rb +18 -26
  49. data/lib/babushka/helpers/uri_helpers.rb +9 -18
  50. data/lib/babushka/lambda_chooser.rb +20 -13
  51. data/lib/babushka/parameter.rb +20 -4
  52. data/lib/babushka/path_checker.rb +72 -0
  53. data/lib/babushka/pkg_helper.rb +38 -13
  54. data/lib/babushka/pkg_helpers/apt_helper.rb +15 -8
  55. data/lib/babushka/pkg_helpers/binpkgsrc_helper.rb +15 -14
  56. data/lib/babushka/pkg_helpers/binports_helper.rb +7 -7
  57. data/lib/babushka/pkg_helpers/brew_helper.rb +17 -25
  58. data/lib/babushka/pkg_helpers/gem_helper.rb +36 -27
  59. data/lib/babushka/pkg_helpers/npm_helper.rb +9 -9
  60. data/lib/babushka/pkg_helpers/pacman_helper.rb +5 -4
  61. data/lib/babushka/pkg_helpers/pip_helper.rb +14 -10
  62. data/lib/babushka/pkg_helpers/unknown_pkg_helper.rb +19 -0
  63. data/lib/babushka/pkg_helpers/yum_helper.rb +1 -1
  64. data/lib/babushka/popen.rb +13 -10
  65. data/lib/babushka/prompt.rb +14 -1
  66. data/lib/babushka/renderable.rb +11 -9
  67. data/lib/babushka/resource.rb +5 -166
  68. data/lib/babushka/run_reporter.rb +12 -3
  69. data/lib/babushka/shell.rb +54 -44
  70. data/lib/babushka/source.rb +41 -20
  71. data/lib/babushka/source_pool.rb +20 -13
  72. data/lib/babushka/system_definitions.rb +11 -3
  73. data/lib/babushka/system_detector.rb +31 -0
  74. data/lib/babushka/system_matcher.rb +53 -0
  75. data/lib/babushka/system_profile.rb +67 -89
  76. data/lib/babushka/task.rb +36 -8
  77. data/lib/babushka/{meta_dep_context.rb → templated_dep_context.rb} +1 -1
  78. data/lib/babushka/vars.rb +46 -4
  79. data/lib/babushka/version_of.rb +35 -17
  80. data/lib/babushka/version_str.rb +12 -8
  81. data/lib/components.rb +9 -8
  82. data/lib/fancypath/fancypath.rb +109 -83
  83. data/lib/inkan/inkan.rb +14 -14
  84. data/lib/{babushka → levenshtein}/levenshtein.rb +0 -0
  85. data/spec/acceptance/acceptance.rb +4 -4
  86. data/spec/acceptance_helper.rb +10 -6
  87. data/spec/babushka/accepts_for_spec.rb +137 -142
  88. data/spec/babushka/accepts_for_support.rb +13 -6
  89. data/spec/babushka/asset_spec.rb +165 -0
  90. data/spec/babushka/cmdline/help_spec.rb +11 -9
  91. data/spec/babushka/cmdline/meet_spec.rb +15 -0
  92. data/spec/babushka/cmdline/version_spec.rb +1 -1
  93. data/spec/babushka/core_patches_spec.rb +9 -0
  94. data/spec/babushka/current_ruby_spec.rb +73 -0
  95. data/spec/babushka/dep_context_spec.rb +27 -13
  96. data/spec/babushka/dep_definer_spec.rb +108 -16
  97. data/spec/babushka/dep_spec.rb +87 -104
  98. data/spec/babushka/dep_template_spec.rb +176 -0
  99. data/spec/babushka/deps_spec.rb +48 -19
  100. data/spec/babushka/gem_helper_spec.rb +46 -59
  101. data/spec/babushka/git_repo_spec.rb +242 -51
  102. data/spec/babushka/ip_spec.rb +11 -11
  103. data/spec/babushka/lambda_chooser_spec.rb +47 -9
  104. data/spec/babushka/parameter_spec.rb +21 -0
  105. data/spec/babushka/path_checker_spec.rb +35 -0
  106. data/spec/babushka/path_helpers_spec.rb +51 -50
  107. data/spec/babushka/prompt_spec.rb +4 -4
  108. data/spec/babushka/renderable_spec.rb +61 -28
  109. data/spec/babushka/shell_helpers_spec.rb +110 -85
  110. data/spec/babushka/shell_spec.rb +15 -0
  111. data/spec/babushka/source_pool_spec.rb +204 -210
  112. data/spec/babushka/source_spec.rb +125 -42
  113. data/spec/babushka/source_support.rb +1 -1
  114. data/spec/babushka/system_profile_spec.rb +86 -49
  115. data/spec/babushka/task_spec.rb +80 -13
  116. data/spec/babushka/vars_spec.rb +2 -1
  117. data/spec/babushka/version_of_spec.rb +29 -2
  118. data/spec/babushka/version_str_spec.rb +91 -65
  119. data/spec/babushka/xml_string_spec.rb +1 -1
  120. data/spec/deps/bad/broken.rb +2 -2
  121. data/spec/deps/bad/working.rb +0 -1
  122. data/spec/deps/good/{meta.rb → template.rb} +0 -0
  123. data/spec/deps/good/test.rb +0 -3
  124. data/spec/deps/outer/deps.rb +0 -2
  125. data/spec/fancypath/fancypath_spec.rb +30 -0
  126. data/spec/inkan/inkan_spec.rb +34 -32
  127. data/spec/spec_helper.rb +7 -50
  128. data/spec/system_detector_spec.rb +70 -0
  129. metadata +163 -177
  130. data/deps/os_x.rb +0 -33
  131. data/deps/templates/ppa.rb +0 -24
  132. data/lib/babushka/core_patches/io.rb +0 -8
  133. data/lib/babushka/dep_runner.rb +0 -85
  134. data/lib/babushka/helpers/suggest_helpers.rb +0 -16
  135. data/lib/babushka/pkg_helpers/base_helper.rb +0 -19
  136. data/lib/babushka/pkg_helpers/macports_helper.rb +0 -22
  137. data/spec/babushka/dep_definer_support.rb +0 -36
  138. data/spec/babushka/meta_dep_definer_spec.rb +0 -127
  139. data/spec/babushka/meta_dep_wrapper_spec.rb +0 -32
  140. data/spec/babushka/resource_spec.rb +0 -141
  141. data/spec/babushka/run_helpers_spec.rb +0 -26
  142. data/spec/babushka/source_pool_support.rb +0 -31
@@ -1,11 +1,10 @@
1
1
  module Babushka
2
2
  class DepContext < DepDefiner
3
- include DepRunner
3
+ include GitHelpers
4
+ include UriHelpers
4
5
 
5
- accepts_list_for :desc
6
6
  accepts_list_for :requires
7
7
  accepts_list_for :requires_when_unmet
8
- accepts_value_for :run_in
9
8
 
10
9
  accepts_block_for :setup
11
10
  accepts_block_for :met?
@@ -14,5 +13,11 @@ module Babushka
14
13
  accepts_block_for :before
15
14
  accepts_block_for :meet
16
15
  accepts_block_for :after
16
+
17
+ private
18
+
19
+ def in_path? provided_list
20
+ PathChecker.in_path? provided_list
21
+ end
17
22
  end
18
23
  end
@@ -31,15 +31,27 @@ module Babushka
31
31
  @dependency = dep
32
32
  @payload = {}
33
33
  @block = block
34
+ @loaded, @failed = false, false
35
+ @current_platform = nil
34
36
  end
35
37
 
36
- def define!
37
- define_params!
38
+ def loaded?; @loaded end
39
+ def failed?; @failed end
38
40
 
39
- unless block.nil?
40
- raise "Dep block arguments aren't supported anymore. Instead, specify parameter names as symbols after the dep name. More details here: http://github.com/benhoskings/babushka/commit/40054c2" if block.arity > 0
41
- instance_eval(&block)
41
+ def define!
42
+ unless loaded? || failed?
43
+ define_elements!
44
+ @loaded, @failed = true, false
42
45
  end
46
+ self
47
+ rescue StandardError => e
48
+ @loaded, @failed = false, true
49
+ raise e
50
+ end
51
+
52
+ def invoke task_name
53
+ define! unless loaded?
54
+ instance_eval(&send(task_name)) unless failed?
43
55
  end
44
56
 
45
57
  def result message, opts = {}
@@ -49,31 +61,45 @@ module Babushka
49
61
  end
50
62
 
51
63
  def met message
52
- result message, :result => true
64
+ removed! :instead => "a truthy return value from met?{} (maybe using #log_ok)"
53
65
  end
54
66
 
55
67
  def unmet message
56
- result message, :result => false
68
+ removed! :instead => "a falsey return value from met?{} (maybe using #log)"
57
69
  end
58
70
 
59
71
  def unmeetable message
72
+ removed! :instead => "#unmeetable!"
73
+ end
74
+
75
+ def unmeetable! message
60
76
  raise Babushka::UnmeetableDep, message
61
77
  end
62
78
 
63
- def file_and_line
64
- get_file_and_line_for(block)
79
+ def source_location
80
+ get_source_location_for(block)
65
81
  end
66
82
 
67
- def file_and_line_for block_name
68
- get_file_and_line_for send(block_name) if has_block? block_name
83
+ def source_location_for block_name
84
+ get_source_location_for send(block_name) if has_block? block_name
69
85
  end
70
86
 
71
- def get_file_and_line_for blk
72
- blk.inspect.scan(/\#\<Proc\:0x[0-9a-f]+\@([^:]+):(\d+)>/).flatten
87
+ def get_source_location_for blk
88
+ if blk.respond_to?(:source_location) # Not present on cruby-1.8.
89
+ blk.source_location
90
+ else
91
+ blk.inspect.scan(/\#\<Proc\:0x[0-9a-f]+\@([^:]+):(\d+)>/).flatten
92
+ end
73
93
  end
74
94
 
75
95
  private
76
96
 
97
+ def define_elements!
98
+ debug "(defining #{dependency.name} against #{dependency.template.contextual_name})"
99
+ define_params!
100
+ instance_eval(&block) unless block.nil?
101
+ end
102
+
77
103
  def define_params!
78
104
  dependency.params.each {|param|
79
105
  if respond_to?(param)
@@ -87,7 +113,11 @@ module Babushka
87
113
  end
88
114
 
89
115
  def pkg_manager
90
- BaseHelper
116
+ UnknownPkgHelper
117
+ end
118
+
119
+ def current_platform
120
+ @current_platform
91
121
  end
92
122
 
93
123
  def on platform, &blk
@@ -100,7 +130,7 @@ module Babushka
100
130
  end
101
131
 
102
132
  def chooser
103
- Base.host.match_list
133
+ Babushka.host.match_list
104
134
  end
105
135
 
106
136
  def chooser_choices
@@ -9,36 +9,33 @@ module Babushka
9
9
  def count
10
10
  @pool.length
11
11
  end
12
-
12
+
13
13
  def names
14
14
  @pool.keys
15
15
  end
16
+
16
17
  def items
17
18
  @pool.values
18
19
  end
20
+
19
21
  def for spec
20
22
  spec.respond_to?(:name) ? @pool[spec.name] : @pool[spec]
21
23
  end
22
24
 
23
25
  def add_dep name, params, block
24
- if self.for name
25
- self.for name
26
- else
26
+ self.for(name) || begin
27
27
  opts = params.extract_options!
28
28
  Dep.new name, @source, params, opts, block
29
29
  end
30
30
  end
31
31
 
32
32
  def add_template name, in_opts, block
33
- MetaDep.for name, @source, in_opts, &block
33
+ DepTemplate.for name, @source, in_opts, &block
34
34
  end
35
35
 
36
36
  def clear!
37
37
  @pool = {}
38
38
  end
39
- def uncache!
40
- items.each {|i| i.send :uncache! }
41
- end
42
39
 
43
40
  def register item
44
41
  raise "Already registered '#{item.name}'." if @pool.has_key?(item.name)
@@ -1,5 +1,24 @@
1
1
  module Babushka
2
- class MetaDep
2
+ # A BaseTemplate is just a blank, passthrough template -- all it does is
3
+ # return DepContext as the context against which standard (i.e. untemplated)
4
+ # deps are defined.
5
+ class BaseTemplate
6
+ def self.contextual_name; name end
7
+ def self.suffixed?; false end
8
+ def self.context_class; DepContext end
9
+ end
10
+
11
+ # This class represents a template against which deps can be defined. The
12
+ # resulting deps are structured just like regular ones, except for the context
13
+ # against which they were defined.
14
+ #
15
+ # Standard deps are defined against DepContext, which makes just the basic dep
16
+ # DSL available, i.e. requires/met?/meet, etc. Templated deps are defined against
17
+ # a subclass of TemplatedDepContext as built by +build_context+.
18
+ #
19
+ # This means that when a templated dep is defined, the context will be a superset
20
+ # of that of a standard dep -- the normal stuff, plus whatever the template adds.
21
+ class DepTemplate
3
22
  include LogHelpers
4
23
 
5
24
  INVALID_NAMES = %w[base]
@@ -54,7 +73,7 @@ module Babushka
54
73
  end
55
74
 
56
75
  def build_context block
57
- Class.new(MetaDepContext, &block).tap {|context|
76
+ Class.new(TemplatedDepContext, &block).tap {|context|
58
77
  shadow = self
59
78
  context.metaclass.send :define_method, :source_template do
60
79
  shadow
data/lib/babushka/dsl.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  module Babushka
2
2
  module DSL
3
+ # Make these helpers directly callable, and private when included.
4
+ module_function
5
+
3
6
  # Use +spec+ to look up a dep. Because +spec+ might include a source
4
7
  # prefix, the dep this method returns could be from any of the currently
5
8
  # known sources.
@@ -3,67 +3,74 @@ module Babushka
3
3
  end
4
4
  class GitRepoExists < GitRepoError
5
5
  end
6
- # This class is used for manipulating git repositories (see {#initialize} how to start). Mostly it provides shortcuts to most often used git commands.
7
- # @example Example of usage:
8
- # @repo ||= Babushka::GitRepo.new('.')
9
- # @repo.clone "https://github.com/benhoskings/babushka"
10
- # @repo.checkout! "devel"
6
+
7
+ # Provides some wrapper methods for interacting concisely with a git
8
+ # repository.
9
+ #
10
+ # The repo is accessed by shelling out to `git` via the {repo_shell}
11
+ # method, which makes sure the repo exists and that the commands are run
12
+ # in the right place. Hence, GitRepo doesn't depend on your current
13
+ # working directory.
14
+ #
15
+ # Most of the methods return a boolean and are used to discover things
16
+ # about the repository, like whether it's {clean?} or {dirty?}, currently
17
+ # {merging?} or {rebasing?}, and whether it's {ahead?} or {behind?} the
18
+ # default remote.
11
19
  #
12
- # For practical example how to use GitRepo class, see Ben Hoskings {https://github.com/benhoskings/babushka-deps/blob/master/push.rb push dep}
20
+ # Some return a piece of data, like {current_head} and {current_branch}.
13
21
  #
14
- # Methods for checking repositrory state:
22
+ # There are also some methods that make simple changes to the repository,
23
+ # like {checkout!} and {reset_hard!}.
15
24
  #
16
- # * {#clean?}
17
- # * {#dirty?}
18
- # * {#include?} ref
19
- # * {#ahead?}
20
- # * {#behind?}
21
- # * {#rebasing?}
22
- # * {#applying?}
23
- # * {#merging?}
24
- # * {#bisecting?}
25
- # * {#rebase_merging?}
26
- # * {#rebasing_interactively?}
25
+ # To perform other operations on the repository like committing or
26
+ # rebasing, check out grit. This class is just a little `git` wrapper.
27
27
  #
28
- # Methods for repository operations:
29
- #
30
- # * {#clone!}
31
- # * {#branch!}
32
- # * {#track!}
33
- # * {#checkout!}
34
- # * {#reset_hard!} refspec
35
-
36
28
  class GitRepo
37
29
  include ShellHelpers
38
30
  extend ShellHelpers
39
31
 
32
+ attr_reader :path
33
+
34
+ # The full path to the root of the repo {path} is within, if it is within
35
+ # one somewhere; otherwise nil.
40
36
  def self.repo_for path
41
- maybe = shell("git rev-parse --git-dir", :cd => path) if path.p.dir?
42
- maybe == '.git' ? path.p : maybe / '..' unless maybe.nil?
43
- end
44
- # @example Initialize repo in current dir
45
- # @repo ||= Babushka::GitRepo.new('.')
46
- def initialize path
47
- @raw_path = path
37
+ maybe = shell?("git rev-parse --git-dir", :cd => path) if path.p.dir?
38
+
39
+ if maybe == '.git'
40
+ path.p
41
+ elsif !maybe.nil?
42
+ maybe / '..'
43
+ end
48
44
  end
49
45
 
50
- def path
51
- @path ||= @raw_path.p
46
+ def initialize path
47
+ @path = path.p
52
48
  end
53
49
 
50
+ # This repo's top-level directory.
54
51
  def root
55
52
  @root ||= self.class.repo_for(path)
56
53
  end
57
54
 
55
+ # This repo's +.git+ directory, where git stores its objects and other repo data.
58
56
  def git_dir
59
57
  root / '.git'
60
58
  end
61
59
 
60
+ # True if +root+ points to an existing git repo.
61
+ #
62
+ # The repo doesn't always have to exist. For example, you can pass a
63
+ # nonexistent path when you initialize a GitRepo, and then call {clone!}
64
+ # on it.
62
65
  def exists?
63
66
  !root.nil? && root.exists?
64
67
  end
65
- # executes shell command setting current directory to repository root
68
+
69
+ # Run +cmd+ on the shell, changing to this repo's {root}. If the repo
70
+ # doesn't exist, a GitRepoError is raised.
66
71
  #
72
+ # A GitRepo with a nonexistent {root} is valid - it will only fail if
73
+ # an operation that requires an existing repo is attempted.
67
74
  def repo_shell cmd, opts = {}, &block
68
75
  if !exists?
69
76
  raise GitRepoError, "There is no repo at #{@path}."
@@ -72,45 +79,75 @@ module Babushka
72
79
  end
73
80
  end
74
81
 
82
+ def repo_shell? cmd, opts = {}
83
+ if !exists?
84
+ raise GitRepoError, "There is no repo at #{@path}."
85
+ else
86
+ shell? cmd, opts.merge(:cd => root)
87
+ end
88
+ end
89
+
90
+
91
+ # True if the repo is clean, i.e. when the content in its index and working
92
+ # copy match the commit that HEAD refers to.
75
93
  def clean?
76
94
  repo_shell("git status") # Sometimes git caches invalid index info; this clears it.
77
95
  repo_shell("git diff-index --name-status HEAD", &:stdout).blank?
78
96
  end
79
97
 
98
+ # The inverse of {clean?} -- true if the content in the repo's index or
99
+ # working copy differs from the commit HEAD refers to.
80
100
  def dirty?
81
101
  !clean?
82
102
  end
83
103
 
104
+ # True if the commit referenced by +ref+ is present somewhere in this repo.
105
+ #
106
+ # Note that the ref being present doesn't mean that it's a parent of +HEAD+,
107
+ # just that it currently resolves to a commit.
84
108
  def include? ref
85
109
  repo_shell("git rev-list -n 1 '#{ref}'")
86
110
  end
87
111
 
112
+ # True if there are any commits in the current branch's history that
113
+ # aren't also present on the corresponding remote branch, or if the
114
+ # remote doesn't exist.
88
115
  def ahead?
89
116
  !remote_branch_exists? ||
90
- !repo_shell("git rev-list origin/#{current_branch}..").split("\n").empty?
117
+ !repo_shell("git rev-list #{current_remote_branch}..").split("\n").empty?
91
118
  end
92
119
 
120
+ # True if there are any commits in the current branch's corresponding remote
121
+ # branch that aren't also present locally, if the remote branch exists.
93
122
  def behind?
94
123
  remote_branch_exists? &&
95
- !repo_shell("git rev-list ..origin/#{current_branch}").split("\n").empty?
124
+ !repo_shell("git rev-list ..#{current_remote_branch}").split("\n").empty?
96
125
  end
97
126
 
127
+ # True if the repo is partway through a rebase of some kind. This could be
128
+ # because one of the commits conflicted when it was replayed, or that the
129
+ # rebase is interactive and is awaiting another command.
98
130
  def rebasing?
99
131
  %w[rebase rebase-apply ../.dotest].any? {|d|
100
132
  (git_dir / d).exists?
101
133
  } or rebase_merging? or rebasing_interactively?
102
134
  end
103
135
 
136
+ # True if a patch is partway through being applied -- perhaps because applying
137
+ # it caused conflicts that are yet to be resolved.
104
138
  def applying?
105
139
  %w[rebase rebase-apply ../.dotest].any? {|d|
106
140
  (git_dir / d / 'applying').exists?
107
141
  }
108
142
  end
109
143
 
144
+ # True if a merge is in progress -- perhaps because it produced conflicts that
145
+ # are yet to be resolved.
110
146
  def merging?
111
147
  (git_dir / 'MERGE_HEAD').exists? or rebase_merging?
112
148
  end
113
149
 
150
+ # True if a bisect is currently in progress.
114
151
  def bisecting?
115
152
  (git_dir / 'BISECT_LOG').exists?
116
153
  end
@@ -127,28 +164,70 @@ module Babushka
127
164
  }
128
165
  end
129
166
 
167
+
168
+
169
+ # An array of the names of all the local branches in this repo.
130
170
  def branches
131
- repo_shell('git branch').split("\n").map {|l| l.sub(/^[* ]+/, '') }
171
+ names = repo_shell('git branch').split("\n").map {|l| l.sub(/^[* ]+/, '') }
172
+ names - ['(no branch)']
132
173
  end
133
174
 
175
+ def all_branches
176
+ names = repo_shell('git branch -a').split("\n").map {|l| l.sub(/^[* ]+/, '') }
177
+ (names - ['(no branch)']).reject {|i|
178
+ i['/origin/HEAD ->']
179
+ }.map {|i|
180
+ i.sub(/^remotes\//, '')
181
+ }
182
+ end
183
+
184
+ # The name of the branch that's currently checked out, if any. If there
185
+ # is no current branch (i.e. if the HEAD is detached), the HEAD's SHA is
186
+ # returned instead.
134
187
  def current_branch
135
188
  repo_shell("cat .git/HEAD").strip.sub(/^.*refs\/heads\//, '')
136
189
  end
137
190
 
191
+ # The namespaced name of the remote branch that the current local branch
192
+ # is tracking, or on origin if the branch isn't tracking an explicit
193
+ # remote branch.
194
+ def current_remote_branch
195
+ branch = current_branch
196
+ "#{remote_for(branch)}/#{branch}"
197
+ end
198
+
199
+ # The short SHA of the repo's current HEAD. This is usually 7 characters,
200
+ # but is longer when extra characters are required to disambiguate it.
138
201
  def current_head
139
202
  repo_shell("git rev-parse --short HEAD")
140
203
  end
141
204
 
205
+ # The full 40 character SHA of the current HEAD.
142
206
  def current_full_head
143
207
  repo_shell("git rev-parse HEAD")
144
208
  end
145
209
 
210
+ # The short SHA of the commit that +ref+ currently refers to.
211
+ def resolve ref
212
+ repo_shell?("git rev-parse --short #{ref}")
213
+ end
214
+
215
+ # The remote assigned to branch in the git config, or 'origin' if none
216
+ # is set. This is the remote that git pushes to and fetches from for this
217
+ # branch by default, and the branch that comparisons like #ahead? and
218
+ # #behind? are made against.
219
+ def remote_for branch
220
+ repo_shell?("git config branch.#{branch}.remote") || 'origin'
221
+ end
222
+
223
+ # True if origin contains a branch of the same name as the current local
224
+ # branch.
146
225
  def remote_branch_exists?
147
- repo_shell('git branch -a').split("\n").map(&:strip).detect {|b|
148
- b[/^(remotes\/)?origin\/#{current_branch}$/]
149
- }
226
+ repo_shell?("git rev-parse refs/remotes/#{current_remote_branch}")
150
227
  end
151
228
 
229
+ # Clone the remote at +from+ to this GitRepo's path. The path must be
230
+ # nonexistent; an error is raised if the local repo already exists.
152
231
  def clone! from
153
232
  raise GitRepoExists, "Can't clone #{from} to existing path #{path}." if exists?
154
233
  shell("git clone '#{from}' '#{path.basename}'", :cd => path.parent, :create => true) {|shell|
@@ -156,20 +235,35 @@ module Babushka
156
235
  }
157
236
  end
158
237
 
159
- def branch! branch
160
- repo_shell("git branch '#{branch}'")
238
+ # Create a new local branch called +branch+ with +ref+ (defaulting to
239
+ # HEAD) as its tip.
240
+ def branch! branch, ref = 'HEAD'
241
+ repo_shell("git branch '#{branch}' '#{ref}'")
161
242
  end
162
243
 
244
+ # Create a new local tracking branch for +branch+, which should be specified
245
+ # as remote/branch. For example, if "origin/next" is passed, a local 'next'
246
+ # branch will be created to track origin's 'next' branch.
163
247
  def track! branch
164
- repo_shell("git checkout -t '#{branch}'")
248
+ repo_shell("git checkout -t '#{branch}' -b '#{branch.sub(/^.*\//, '')}'")
249
+ end
250
+
251
+ # Check out the supplied ref, detaching the HEAD if the named ref
252
+ # isn't a branch.
253
+ def checkout! ref
254
+ repo_shell("git checkout '#{ref}'")
165
255
  end
166
256
 
167
- def checkout! branch
168
- repo_shell("git checkout '#{branch}'")
257
+ # Check out the supplied ref, detaching the HEAD. If the ref is a branch
258
+ # or tag, HEAD will reference the commit at the tip of the ref.
259
+ def detach! ref = 'HEAD'
260
+ repo_shell("git checkout '#{resolve(ref)}'")
169
261
  end
170
262
 
171
- def reset_hard! refspec = 'HEAD'
172
- repo_shell("git reset --hard #{refspec}")
263
+ # Reset the repo to the given ref, discarding changes in the index and
264
+ # working copy.
265
+ def reset_hard! ref = 'HEAD'
266
+ repo_shell("git reset --hard '#{ref}'")
173
267
  end
174
268
 
175
269
  def inspect