mercurial-ruby 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. data/.document +5 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +32 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +86 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/lib/mercurial-ruby.rb +66 -0
  9. data/lib/mercurial-ruby/branch.rb +45 -0
  10. data/lib/mercurial-ruby/changed_file.rb +51 -0
  11. data/lib/mercurial-ruby/command.rb +77 -0
  12. data/lib/mercurial-ruby/commit.rb +152 -0
  13. data/lib/mercurial-ruby/config_file.rb +119 -0
  14. data/lib/mercurial-ruby/configuration.rb +14 -0
  15. data/lib/mercurial-ruby/diff.rb +50 -0
  16. data/lib/mercurial-ruby/factories/branch_factory.rb +91 -0
  17. data/lib/mercurial-ruby/factories/changed_file_factory.rb +50 -0
  18. data/lib/mercurial-ruby/factories/commit_factory.rb +154 -0
  19. data/lib/mercurial-ruby/factories/diff_factory.rb +63 -0
  20. data/lib/mercurial-ruby/factories/hook_factory.rb +45 -0
  21. data/lib/mercurial-ruby/factories/node_factory.rb +111 -0
  22. data/lib/mercurial-ruby/factories/tag_factory.rb +48 -0
  23. data/lib/mercurial-ruby/file_index.rb +209 -0
  24. data/lib/mercurial-ruby/helper.rb +23 -0
  25. data/lib/mercurial-ruby/hook.rb +23 -0
  26. data/lib/mercurial-ruby/manifest.rb +58 -0
  27. data/lib/mercurial-ruby/node.rb +100 -0
  28. data/lib/mercurial-ruby/repository.rb +94 -0
  29. data/lib/mercurial-ruby/root_node.rb +27 -0
  30. data/lib/mercurial-ruby/shell.rb +64 -0
  31. data/lib/mercurial-ruby/style.rb +23 -0
  32. data/lib/mercurial-ruby/tag.rb +33 -0
  33. data/lib/stdlib_exts/string.rb +12 -0
  34. data/lib/styles/changeset.style +5 -0
  35. data/lib/styles/file_index.style +3 -0
  36. data/mercurial-ruby.gemspec +227 -0
  37. data/test/fixtures.rb +28 -0
  38. data/test/fixtures/test-repo/.DotFile +1 -0
  39. data/test/fixtures/test-repo/.hg/00changelog.i +0 -0
  40. data/test/fixtures/test-repo/.hg/branch +1 -0
  41. data/test/fixtures/test-repo/.hg/cache/branchheads +6 -0
  42. data/test/fixtures/test-repo/.hg/cache/tags +7 -0
  43. data/test/fixtures/test-repo/.hg/dirstate +0 -0
  44. data/test/fixtures/test-repo/.hg/hgrc +3 -0
  45. data/test/fixtures/test-repo/.hg/last-message.txt +1 -0
  46. data/test/fixtures/test-repo/.hg/requires +4 -0
  47. data/test/fixtures/test-repo/.hg/store/00changelog.i +0 -0
  48. data/test/fixtures/test-repo/.hg/store/00manifest.i +0 -0
  49. data/test/fixtures/test-repo/.hg/store/data/_file _with _whitespace.pdf.i +0 -0
  50. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e.txt.i +0 -0
  51. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e2.txt.i +0 -0
  52. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e3.txt.i +0 -0
  53. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e4.txt.i +0 -0
  54. data/test/fixtures/test-repo/.hg/store/data/_r_e_a_d_m_e.markdown.i +0 -0
  55. data/test/fixtures/test-repo/.hg/store/data/_r_e_a_d_m_e.markup.i +0 -0
  56. data/test/fixtures/test-repo/.hg/store/data/_rakefile.i +0 -0
  57. data/test/fixtures/test-repo/.hg/store/data/_rakefile2.i +0 -0
  58. data/test/fixtures/test-repo/.hg/store/data/_rakefile3.i +0 -0
  59. data/test/fixtures/test-repo/.hg/store/data/check ~5c this ~5c out ~22 now.i +0 -0
  60. data/test/fixtures/test-repo/.hg/store/data/directory two/minitest__mixin.rb.i +0 -0
  61. data/test/fixtures/test-repo/.hg/store/data/directory two/options.rb.i +0 -0
  62. data/test/fixtures/test-repo/.hg/store/data/directory two/rdoc__mixin.rb.i +0 -0
  63. data/test/fixtures/test-repo/.hg/store/data/directory two/rspec__mixin.rb.i +0 -0
  64. data/test/fixtures/test-repo/.hg/store/data/directory two/shindo__mixin.rb.i +0 -0
  65. data/test/fixtures/test-repo/.hg/store/data/directory two/shoulda__mixin.rb.i +0 -0
  66. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_gemfile.i +0 -0
  67. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_l_i_c_e_n_s_e.txt.i +0 -0
  68. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_r_e_a_d_m_e.rdoc.i +0 -0
  69. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_rakefile.i +0 -0
  70. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/bacon/flunking.rb.i +0 -0
  71. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/bacon/helper.rb.i +0 -0
  72. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/bundler__setup.erb.i +0 -0
  73. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/features/default.feature.i +0 -0
  74. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/features/support/env.rb.i +0 -0
  75. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/jeweler__tasks.erb.i +0 -0
  76. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/micronaut/flunking.rb.i +0 -0
  77. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/micronaut/helper.rb.i +0 -0
  78. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/minitest/flunking.rb.i +0 -0
  79. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/minitest/helper.rb.i +0 -0
  80. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/other__tasks.erb.i +0 -0
  81. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/riot/flunking.rb.i +0 -0
  82. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/riot/helper.rb.i +0 -0
  83. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/rspec/flunking.rb.i +0 -0
  84. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/rspec/helper.rb.i +0 -0
  85. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/rspec/~2erspec.i +0 -0
  86. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shindo/flunking.rb.i +0 -0
  87. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shindo/helper.rb.i +0 -0
  88. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shoulda/flunking.rb.i +0 -0
  89. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shoulda/helper.rb.i +0 -0
  90. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testspec/flunking.rb.i +0 -0
  91. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testspec/helper.rb.i +0 -0
  92. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testunit/flunking.rb.i +0 -0
  93. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testunit/helper.rb.i +0 -0
  94. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/~2edocument.i +0 -0
  95. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/~2egitignore.i +0 -0
  96. data/test/fixtures/test-repo/.hg/store/data/directory two/~2e_d_s___store.i +0 -0
  97. data/test/fixtures/test-repo/.hg/store/data/directory__1/rubygems__dot__org__tasks.rb.i +0 -0
  98. data/test/fixtures/test-repo/.hg/store/data/directory__1/rubygems__tasks.rb.i +0 -0
  99. data/test/fixtures/test-repo/.hg/store/data/directory__1/specification.rb.i +0 -0
  100. data/test/fixtures/test-repo/.hg/store/data/directory__1/tasks.rb.i +0 -0
  101. data/test/fixtures/test-repo/.hg/store/data/directory__1/~2e_d_s___store.i +0 -0
  102. data/test/fixtures/test-repo/.hg/store/data/empty-file.i +0 -0
  103. data/test/fixtures/test-repo/.hg/store/data/goose.png.i +0 -0
  104. data/test/fixtures/test-repo/.hg/store/data/goose/pretty-thing.txt.i +0 -0
  105. data/test/fixtures/test-repo/.hg/store/data/new-directory/another-boring-file.i +0 -0
  106. data/test/fixtures/test-repo/.hg/store/data/new-directory/something.csv.i +0 -0
  107. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/_e_u_l_a5seat___chin___sim02.03.04.pdf.d +0 -0
  108. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/_e_u_l_a5seat___chin___sim02.03.04.pdf.i +0 -0
  109. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/_file _with _whitespace.pdf.i +0 -0
  110. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/beansprout.png.i +0 -0
  111. data/test/fixtures/test-repo/.hg/store/data/new-file.i +0 -0
  112. data/test/fixtures/test-repo/.hg/store/data/old-directory/minitest__mixin.rb.i +0 -0
  113. data/test/fixtures/test-repo/.hg/store/data/old-directory/options.rb.i +0 -0
  114. data/test/fixtures/test-repo/.hg/store/data/old-directory/rspec__mixin.rb.i +0 -0
  115. data/test/fixtures/test-repo/.hg/store/data/old-directory/shindo__mixin.rb.i +0 -0
  116. data/test/fixtures/test-repo/.hg/store/data/old-directory/testunit__mixin.rb.i +0 -0
  117. data/test/fixtures/test-repo/.hg/store/data/old-directory/yard__mixin.rb.i +0 -0
  118. data/test/fixtures/test-repo/.hg/store/data/riot__mixin.rb.i +0 -0
  119. data/test/fixtures/test-repo/.hg/store/data/riot__mixin__copy.rb.i +0 -0
  120. data/test/fixtures/test-repo/.hg/store/data/style.i +0 -0
  121. data/test/fixtures/test-repo/.hg/store/data/super-cow.i +0 -0
  122. data/test/fixtures/test-repo/.hg/store/data/superman.txt.i +0 -0
  123. data/test/fixtures/test-repo/.hg/store/data/testspec__mixin.rb.i +0 -0
  124. data/test/fixtures/test-repo/.hg/store/data/testspec__mixin__new.rb.i +0 -0
  125. data/test/fixtures/test-repo/.hg/store/data/~2e_dot_file.i +0 -0
  126. data/test/fixtures/test-repo/.hg/store/data/~2ehgignore.i +0 -0
  127. data/test/fixtures/test-repo/.hg/store/data/~2ehgtags.i +0 -0
  128. data/test/fixtures/test-repo/.hg/store/fncache +79 -0
  129. data/test/fixtures/test-repo/.hg/store/undo +0 -0
  130. data/test/fixtures/test-repo/.hg/undo.bookmarks +0 -0
  131. data/test/fixtures/test-repo/.hg/undo.branch +1 -0
  132. data/test/fixtures/test-repo/.hg/undo.desc +2 -0
  133. data/test/fixtures/test-repo/.hg/undo.dirstate +0 -0
  134. data/test/fixtures/test-repo/.hgignore +1 -0
  135. data/test/fixtures/test-repo/.hgtags +1 -0
  136. data/test/fixtures/test-repo/LICENSE3.txt +15 -0
  137. data/test/fixtures/test-repo/LICENSE4.txt +17 -0
  138. data/test/fixtures/test-repo/README.markup +218 -0
  139. data/test/fixtures/test-repo/Rakefile3 +83 -0
  140. data/test/fixtures/test-repo/check // this // out /" now" "b/data/test/fixtures/test-repo/check // this // out / → now +0 -0
  141. data/test/fixtures/test-repo/empty-file +0 -0
  142. data/test/fixtures/test-repo/goose.png +0 -0
  143. data/test/fixtures/test-repo/goose/pretty-thing.txt +0 -0
  144. data/test/fixtures/test-repo/new-directory/another-boring-file +78 -0
  145. data/test/fixtures/test-repo/new-directory/something.csv +1 -0
  146. data/test/fixtures/test-repo/new-directory/subdirectory/EULA5seat_Chin_Sim02.03.04.pdf +0 -0
  147. data/test/fixtures/test-repo/new-directory/subdirectory/File With Whitespace.pdf b/data/test/fixtures/test-repo/new-directory/subdirectory/File With → Whitespace.pdf +0 -0
  148. data/test/fixtures/test-repo/new-directory/subdirectory/beansprout.png +0 -0
  149. data/test/fixtures/test-repo/riot_mixin.rb +45 -0
  150. data/test/fixtures/test-repo/style +4 -0
  151. data/test/fixtures/test-repo/superman.txt +1 -0
  152. data/test/fixtures/test-repo/testspec_mixin_new.rb +44 -0
  153. data/test/helper.rb +41 -0
  154. data/test/test_branch_factory.rb +46 -0
  155. data/test/test_changed_file.rb +46 -0
  156. data/test/test_changed_file_factory.rb +16 -0
  157. data/test/test_command.rb +62 -0
  158. data/test/test_commit.rb +66 -0
  159. data/test/test_commit_factory.rb +101 -0
  160. data/test/test_config_file.rb +105 -0
  161. data/test/test_configuration.rb +26 -0
  162. data/test/test_diff.rb +39 -0
  163. data/test/test_diff_factory.rb +38 -0
  164. data/test/test_file_index.rb +113 -0
  165. data/test/test_hook.rb +39 -0
  166. data/test/test_hook_factory.rb +40 -0
  167. data/test/test_manifest.rb +39 -0
  168. data/test/test_node.rb +34 -0
  169. data/test/test_node_factory.rb +125 -0
  170. data/test/test_repository.rb +58 -0
  171. data/test/test_shell.rb +33 -0
  172. data/test/test_tag_factory.rb +27 -0
  173. metadata +328 -0
@@ -0,0 +1,154 @@
1
+ module Mercurial
2
+
3
+ #
4
+ # This class represents a factory for {Mercurial::Commit Commit} instances.
5
+ #
6
+ class CommitFactory
7
+ include Mercurial::Helper
8
+
9
+ # Instance of {Mercurial::Repository Repository}.
10
+ attr_reader :repository
11
+
12
+ def initialize(repository) #:nodoc:
13
+ @repository = repository
14
+ end
15
+
16
+ # Return an array of {Mercurial::Commit Commit} instances for all changesets in the repository.
17
+ #
18
+ # == Example:
19
+ # repository.commits.all
20
+ #
21
+ def all
22
+ hg_to_array ["log --style ?", style], changeset_separator do |line|
23
+ build(line)
24
+ end
25
+ end
26
+
27
+ # Run a block for every {Mercurial::Commit Commit} instance of all changesets in the repository.
28
+ #
29
+ # == Example:
30
+ # repository.commits.each {|commit| ... }
31
+ #
32
+ def each(&block)
33
+ all.each do |commit|
34
+ block.call(commit)
35
+ end
36
+ end
37
+
38
+ # Count all changesets in the repository.
39
+ #
40
+ # == Example:
41
+ # repository.commits.count
42
+ #
43
+ def count
44
+ hg_to_array %Q[log --template "{node}\n"], "\n" do |line|
45
+ line
46
+ end.size
47
+ end
48
+
49
+ # Return an array of {Mercurial::Commit Commit} instances for changesets in a specific branch.
50
+ #
51
+ # == Example:
52
+ # repository.commits.by_branch('brancname')
53
+ #
54
+ def by_branch(branch)
55
+ hg_to_array ["log -b ? --style ?", branch, style], changeset_separator do |line|
56
+ build(line)
57
+ end
58
+ end
59
+
60
+ # Return an instance of {Mercurial::Commit Commit} for a changeset with a specified id.
61
+ #
62
+ # == Example:
63
+ # repository.commits.by_hash_id('291a498f04e9')
64
+ #
65
+ def by_hash_id(hash)
66
+ build do
67
+ hg(["log -r ? --style ?", hash, style])
68
+ end
69
+ end
70
+
71
+ # Return an array of {Mercurial::Commit Commit} instances for changesets with specified ids.
72
+ #
73
+ # == Example:
74
+ # repository.commits.by_hash_ids('291a498f04e9', '63f70b2314ed')
75
+ #
76
+ def by_hash_ids(*args)
77
+ if args.size == 1 && args.first.kind_of?(Array)
78
+ array = args.first
79
+ else
80
+ array = args
81
+ end
82
+ return [] if array.empty?
83
+
84
+ args = array.map{|hash| " -r#{ hash }"}
85
+ hg_to_array ["log#{ args } --style ?", style], changeset_separator do |line|
86
+ build(line)
87
+ end
88
+ end
89
+
90
+ # Return an array of {Mercurial::Commit Commit} instances for a specified range of changeset ids.
91
+ #
92
+ # == Example:
93
+ # repository.commits.for_range('bf6386c0a0cc', '63f70b2314ed')
94
+ #
95
+ def for_range(hash_a, hash_b)
96
+ hg_to_array ["log -r ?:? --style ?", hash_a, hash_b, style], changeset_separator do |line|
97
+ build(line)
98
+ end
99
+ end
100
+
101
+ # Return an instance of {Mercurial::Commit Commit} for a repository's tip changeset (latest).
102
+ #
103
+ # == Example:
104
+ # repository.commits.tip
105
+ #
106
+ def tip
107
+ build do
108
+ hg(["tip --style ?", style])
109
+ end
110
+ end
111
+ alias :latest :tip
112
+
113
+ protected
114
+
115
+ def changeset_separator
116
+ Mercurial::Style::CHANGESET_SEPARATOR
117
+ end
118
+
119
+ def field_separator
120
+ Mercurial::Style::FIELD_SEPARATOR
121
+ end
122
+
123
+ def build(data=nil, &block)
124
+ data ||= block.call
125
+ return if data.empty?
126
+ data = data.gsub(/#{ Regexp.escape(changeset_separator) }$/, '')
127
+ data = data.split(field_separator)
128
+ commit = Mercurial::Commit.new(
129
+ repository,
130
+ :hash_id => data[0],
131
+ :author => data[1],
132
+ :author_email => data[2],
133
+ :date => data[3],
134
+ :message => data[4],
135
+ :changed_files => [data[5], data[6], data[7], data[8]],
136
+ :branches_names => data[9],
137
+ :tags_names => data[10],
138
+ :parents => data[11]
139
+ )
140
+
141
+ if commit.blank?
142
+ nil
143
+ else
144
+ commit
145
+ end
146
+ end
147
+
148
+ def style
149
+ Mercurial::Style.changeset
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,63 @@
1
+ module Mercurial
2
+
3
+ #
4
+ # This class represents a factory for {Mercurial::Diff Diff} instances.
5
+ #
6
+ class DiffFactory
7
+ include Mercurial::Helper
8
+
9
+ # Instance of {Mercurial::Repository Repository}.
10
+ attr_reader :repository
11
+
12
+ def initialize(repository)
13
+ @repository = repository
14
+ end
15
+
16
+ # Returns an array of {Mercurial::Diff Diff} instances for a specified
17
+ # instance of {Mercurial::Commit Commit}. Represents changeset's diffs.
18
+ #
19
+ # == Example:
20
+ # commit = repository.commits.by_hash_id('291a498f04e9')
21
+ # repository.diffs.for_commit(commit)
22
+ #
23
+ def for_commit(commit)
24
+ [].tap do |returning|
25
+ data = hg(["diff -c ?", commit.hash_id])
26
+ chunks = data.split(/^diff/)[1..-1]
27
+ unless chunks.nil?
28
+ chunks.map do |piece|
29
+ piece = "diff" << piece
30
+ returning << build(commit, piece)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def build(commit, data)
39
+ return if data.empty?
40
+ hash_a, hash_b = *data.scan(/^diff -r (\w+) -r (\w+)/).first
41
+
42
+ if binary_file = data.scan(/^Binary file (.+) has changed/).flatten.first
43
+ file_a = binary_file
44
+ body = 'Binary files differ'
45
+ else
46
+ file_a = data.scan(/^--- (?:a\/(.+)|\/dev\/null)\t/).flatten.first
47
+ file_b = data.scan(/^\+\+\+ (?:b\/(.+)|\/dev\/null)\t/).flatten.first
48
+ body = data[data.index("\n")+1..-1]
49
+ end
50
+
51
+ Mercurial::Diff.new(commit,
52
+ :hash_a => hash_a,
53
+ :hash_b => hash_b,
54
+ :file_a => file_a,
55
+ :file_b => file_b,
56
+ :body => body,
57
+ :binary => !!binary_file
58
+ )
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,45 @@
1
+ module Mercurial
2
+
3
+ class HookFactory
4
+
5
+ attr_reader :repository
6
+
7
+ def initialize(repository)
8
+ @repository = repository
9
+ end
10
+
11
+ def all
12
+ [].tap do |returning|
13
+ repository.config.find_header('hooks').each_pair do |name, value|
14
+ returning << build(name, value)
15
+ end
16
+ end
17
+ end
18
+
19
+ def by_name(name)
20
+ all.find do |h|
21
+ h.name == name
22
+ end
23
+ end
24
+
25
+ def add(name, value)
26
+ build(name, value).tap do |hook|
27
+ hook.save
28
+ end
29
+ end
30
+
31
+ def remove(name)
32
+ if hook = by_name(name)
33
+ hook.destroy!
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def build(name, value)
40
+ Mercurial::Hook.new(repository, name, value)
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,111 @@
1
+ module Mercurial
2
+ class NodeMissing < Error; end
3
+
4
+ #
5
+ # This class represents a factory for {Mercurial::Node Node} instances.
6
+ #
7
+ class NodeFactory
8
+ include Mercurial::Helper
9
+
10
+ # Instance of {Mercurial::Repository Repository}.
11
+ attr_reader :repository
12
+
13
+ def initialize(repository)
14
+ @repository = repository
15
+ end
16
+
17
+ # Finds a specified file or a directory in the repository at a specified revision.
18
+ # Returns an instance of {Mercurial::Node Node}.
19
+ #
20
+ # Will find node in the latest version of repo if revision is ommitted.
21
+ # Will return nil if node wasn't found.
22
+ #
23
+ # == Example:
24
+ # repository.nodes.find('/')
25
+ # repository.nodes.find('some-fancy-directory/Weird File Name.pdf', '291a498f04e9')
26
+ # repository.nodes.find('some-fancy-directory/subdirectory/', '291a498f04e9')
27
+ #
28
+ def find(path, revision=nil)
29
+ revision ||= 'tip'
30
+ return RootNode.new(:repository => repository, :revision => revision) if path == '/'
31
+ entry = repository.manifest.scan_for_path(path, revision).first
32
+ return unless entry
33
+ if exact_path = entry[3].scan(/^(#{ Regexp.escape(path.without_trailing_slash) }\/)/).flatten.first
34
+ name = exact_path.split('/').last + '/'
35
+ build(
36
+ :path => exact_path,
37
+ :name => name,
38
+ :revision => revision
39
+ )
40
+ else
41
+ build(
42
+ :path => entry[3],
43
+ :name => entry[3].split('/').last,
44
+ :revision => revision,
45
+ :nodeid => entry[0],
46
+ :fmode => entry[1],
47
+ :exec => entry[2]
48
+ )
49
+ end
50
+ end
51
+
52
+ # Same as +find+ but will raise a NodeMissing exception if node wasn't found.
53
+ def find!(path, revision=nil)
54
+ find(path, revision) || raise(NodeMissing, "#{ path } at revision #{ revision }")
55
+ end
56
+
57
+ # Find all entries (files and directories) inside a specified path and revision.
58
+ # Returns an array of {Mercurial::Node Node} instances.
59
+ #
60
+ # Will find node in the latest version of repo if revision is ommitted.
61
+ #
62
+ # == Example:
63
+ # repository.nodes.entries_for('/')
64
+ # repository.nodes.entries_for('some-fancy-directory/subdirectory/', '291a498f04e9')
65
+ #
66
+ def entries_for(path, revision=nil, parent=nil)
67
+ revision ||= 'tip'
68
+ entries = []
69
+ manifest_entries = repository.manifest.scan_for_path(path, revision)
70
+ manifest_entries.each do |me|
71
+ path_without_source = me[3].gsub(/^#{ Regexp.escape(path.without_trailing_slash) }\//, '')
72
+ entry_name = path_without_source.split('/').first
73
+ entry_path = File.join(path, entry_name).gsub(/^\//, '')
74
+ dir = me[3].scan(/^(#{ Regexp.escape(entry_path) }\/)/).flatten.first ? true : false
75
+ entry_name << '/' if dir
76
+
77
+ entries << build(
78
+ :path => entry_path,
79
+ :name => entry_name,
80
+ :revision => revision,
81
+ :nodeid => (me[0] unless dir),
82
+ :fmode => dir ? nil : me[1],
83
+ :exec => dir ? nil : me[2],
84
+ :parent => parent
85
+ )
86
+ end
87
+
88
+ entries = entries.inject({}) do |hash,item|
89
+ hash[item.name] ||= item
90
+ hash
91
+ end.values
92
+ end
93
+
94
+ private
95
+
96
+ def build(opts={})
97
+ Mercurial::Node.new(
98
+ :repository => repository,
99
+ :path => opts[:path],
100
+ :name => opts[:name],
101
+ :revision => opts[:revision],
102
+ :nodeid => opts[:nodeid],
103
+ :fmode => opts[:fmode],
104
+ :executable => opts[:exec],
105
+ :parent => opts[:parent]
106
+ )
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,48 @@
1
+ module Mercurial
2
+
3
+ #
4
+ # This class represents a factory for {Mercurial::Tag Tag} instances.
5
+ #
6
+ class TagFactory
7
+ include Mercurial::Helper
8
+
9
+ # Instance of {Mercurial::Repository Repository}.
10
+ attr_reader :repository
11
+
12
+ def initialize(repository)
13
+ @repository = repository
14
+ end
15
+
16
+ # Return an array of {Mercurial::Tag Tag} instances for all tags in the repository.
17
+ #
18
+ # == Example:
19
+ # repository.tags.all
20
+ #
21
+ def all
22
+ hg_to_array "tags" do |line|
23
+ build(line)
24
+ end
25
+ end
26
+
27
+ # Return a {Mercurial::Tag Tag} instance for a tag with a specified name.
28
+ #
29
+ # == Example:
30
+ # repository.tags.by_name('tagname')
31
+ #
32
+ def by_name(name)
33
+ all.find do |b|
34
+ b.name == name
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def build(data)
41
+ name, hash_id = *data.scan(/([\w-]+)\s+\d+:(\w+)\s*/).first
42
+ return if name == 'tip'
43
+ Mercurial::Tag.new(repository, name, hash_id)
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,209 @@
1
+ module Mercurial
2
+
3
+ # This class was ported from grit.
4
+ #
5
+ # This implements a file-based 'file index', an simple index of
6
+ # all of the reachable commits in a repo, along with the parents
7
+ # and which files were modified during each commit.
8
+ #
9
+ # This class creates and reads a file named '[.hg]/file-index'.
10
+ #
11
+ class FileIndex
12
+ include Mercurial::Helper
13
+
14
+ class IndexFileNotFound < StandardError
15
+ end
16
+
17
+ class UnsupportedRef < StandardError
18
+ end
19
+
20
+ class << self
21
+ attr_accessor :max_file_size
22
+ end
23
+
24
+ self.max_file_size = 10_000_000 # ~10M
25
+
26
+ attr_reader :repository
27
+
28
+ # initializes index given repository
29
+ def initialize(repository)
30
+ @repository = repository
31
+ end
32
+
33
+ def reload
34
+ @_read_complete = false
35
+ end
36
+
37
+ # updates file index
38
+ def update(oldrev=nil, newrev=nil)
39
+ if index_file_exists? && oldrev != "0"*40
40
+ hg([
41
+ "log --debug -r ?:? --style ? >> ?",
42
+ oldrev, newrev, Style.file_index, path
43
+ ])
44
+ else
45
+ hg(["log --debug -r : --style ? > ?", Style.file_index, path])
46
+ end
47
+ end
48
+
49
+ # returns count of all commits
50
+ def count_all
51
+ read_if_needed
52
+ @sha_count
53
+ end
54
+
55
+ # returns count of all commits reachable from SHA
56
+ # note: originally did this recursively, but ruby gets pissed about that on
57
+ # really big repos where the stack level gets 'too deep' (thats what she said)
58
+ def count(commit_sha)
59
+ read_if_needed
60
+ commits_from(commit_sha).size
61
+ end
62
+
63
+ # builds a list of all commits reachable from a single commit
64
+ def commits_from(commit_sha)
65
+ raise UnsupportedRef if commit_sha.is_a? Array
66
+ read_if_needed
67
+
68
+ already = {}
69
+ final = []
70
+ left_to_do = [commit_sha]
71
+
72
+ while commit_sha = left_to_do.shift
73
+ next if already[commit_sha]
74
+
75
+ final << commit_sha
76
+ already[commit_sha] = true
77
+
78
+ commit = @commit_index[commit_sha]
79
+ commit[:parents].each do |sha|
80
+ left_to_do << sha
81
+ end if commit
82
+ end
83
+
84
+ sort_commits(final)
85
+ end
86
+
87
+ # returns files changed at commit sha
88
+ def files(commit_sha)
89
+ read_if_needed
90
+ if commit = @commit_index[commit_sha]
91
+ commit[:files]
92
+ else
93
+ []
94
+ end
95
+ end
96
+
97
+ # returns all commits for a file
98
+ def commits_for(file)
99
+ read_if_needed
100
+ @all_files[file] || []
101
+ end
102
+
103
+ # returns the shas of the last commits for all
104
+ # the files in [] from commit_sha
105
+ # files_matcher can be a regexp or an array
106
+ def last_commits(commit_sha, files_matcher)
107
+ read_if_needed
108
+ acceptable = commits_from(commit_sha)
109
+
110
+ matches = {}
111
+
112
+ if files_matcher.is_a? Regexp
113
+ files = @all_files.keys.select { |file| file =~ files_matcher }
114
+ files_matcher = files
115
+ end
116
+
117
+ if files_matcher.is_a? Array
118
+ # find the last commit for each file in the array
119
+ files_matcher.each do |f|
120
+ @all_files[f].each do |try|
121
+ if acceptable.include?(try)
122
+ matches[f] = try
123
+ break
124
+ end
125
+ end if @all_files[f]
126
+ end
127
+ end
128
+
129
+ matches
130
+ end
131
+
132
+ def destroy!
133
+ FileUtils.rm_f(path)
134
+ end
135
+
136
+ def path
137
+ File.join(repository.dothg_path, 'file-index')
138
+ end
139
+
140
+ private
141
+
142
+ def index_file_exists?
143
+ FileTest.exists?(path)
144
+ end
145
+
146
+ def index_file_valid?
147
+ File.file?(path) && (File.size(path) < Mercurial::FileIndex.max_file_size)
148
+ end
149
+
150
+ def sort_commits(sha_array)
151
+ read_if_needed
152
+ sha_array.sort { |a, b| @commit_order[b].to_i <=> @commit_order[a].to_i }
153
+ end
154
+
155
+ def read_if_needed
156
+ if @_read_complete
157
+ true
158
+ else
159
+ begin
160
+ read_index
161
+ rescue IndexFileNotFound => e
162
+ if @_tried_updating
163
+ raise e
164
+ else
165
+ @_tried_updating = true
166
+ update
167
+ retry
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ def read_index
174
+ raise IndexFileNotFound unless index_file_valid?
175
+ f = File.new(path)
176
+ @sha_count = 0
177
+ @commit_index = {}
178
+ @commit_order = {}
179
+ @all_files = {}
180
+ while line = f.gets
181
+ if /^(\w{40})/.match(line)
182
+ shas = line.scan(/(\w{40})/)
183
+ current_sha = shas.shift.first
184
+ parents = shas.map { |sha| sha.first }
185
+ parents = parents.delete_if { |sha| sha == '0'*40 }
186
+ @commit_index[current_sha] = {:files => [], :parents => parents }
187
+ @commit_order[current_sha] = @sha_count
188
+ @sha_count += 1
189
+ else
190
+ file_name = line.chomp
191
+ unless file_name.empty?
192
+ tree = ''
193
+ File.dirname(file_name).split('/').each do |part|
194
+ next if part == '.'
195
+ tree += part + '/'
196
+ @all_files[tree] ||= []
197
+ @all_files[tree].unshift(current_sha)
198
+ end
199
+ @all_files[file_name] ||= []
200
+ @all_files[file_name].unshift(current_sha)
201
+ @commit_index[current_sha][:files] << file_name
202
+ end
203
+ end
204
+ end
205
+ @_read_complete = true
206
+ end
207
+
208
+ end
209
+ end