amp-git 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/Gemfile +15 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +68 -0
- data/VERSION +1 -0
- data/features/amp-git.feature +9 -0
- data/features/step_definitions/amp-git_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/lib/amp-git/encoding/binary_delta.rb +171 -0
- data/lib/amp-git/repo_format/changeset.rb +348 -0
- data/lib/amp-git/repo_format/commit_object.rb +87 -0
- data/lib/amp-git/repo_format/index.rb +169 -0
- data/lib/amp-git/repo_format/loose_object.rb +78 -0
- data/lib/amp-git/repo_format/packfile.rb +263 -0
- data/lib/amp-git/repo_format/packfile_index.rb +196 -0
- data/lib/amp-git/repo_format/raw_object.rb +56 -0
- data/lib/amp-git/repo_format/staging_area.rb +215 -0
- data/lib/amp-git/repo_format/tag_object.rb +87 -0
- data/lib/amp-git/repo_format/tree_object.rb +98 -0
- data/lib/amp-git/repo_format/versioned_file.rb +133 -0
- data/lib/amp-git/repositories/local_repository.rb +192 -0
- data/lib/amp-git/repository.rb +57 -0
- data/lib/amp-git.rb +49 -0
- data/lib/amp_plugin.rb +1 -0
- data/spec/amp-git_spec.rb +15 -0
- data/spec/repository_spec.rb +74 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +29 -0
- data/test/index_tests/index +0 -0
- data/test/index_tests/test_helper.rb +16 -0
- data/test/index_tests/test_index.rb +69 -0
- data/test/packfile_tests/hasindex.idx +0 -0
- data/test/packfile_tests/hasindex.pack +0 -0
- data/test/packfile_tests/pack-4e1941122fd346526b0a3eee2d92f3277a0092cd.pack +0 -0
- data/test/packfile_tests/pack-d23ff2538f970371144ae7182c28730b11eb37c1.idx +0 -0
- data/test/packfile_tests/test_helper.rb +16 -0
- data/test/packfile_tests/test_packfile.rb +75 -0
- data/test/packfile_tests/test_packfile_index_v2.rb +90 -0
- data/test/packfile_tests/test_packfile_with_index.rb +76 -0
- data/test/test_commit_object.rb +60 -0
- data/test/test_git_delta.rb +67 -0
- data/test/test_helper.rb +71 -0
- data/test/test_loose_object.rb +51 -0
- data/test/test_tag_object.rb +72 -0
- data/test/test_tree_object.rb +55 -0
- metadata +215 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
# This was written by reading the Git Book. No source code was
|
16
|
+
# examined to produce this code. It is the original work of its
|
17
|
+
# creators, Michael Edgar and Ari Brown.
|
18
|
+
#
|
19
|
+
# http://book.git-scm.com/7_the_packfile.html
|
20
|
+
# http://git.rsbx.net/Documents/Git_Data_Formats.txt
|
21
|
+
# http://repo.or.cz/w/git.git?a=blob;f=Documentation/technical/pack-format.txt;h=1803e64e465fa4f8f0fe520fc0fd95d0c9def5bd;hb=HEAD
|
22
|
+
|
23
|
+
module Amp
|
24
|
+
module Core
|
25
|
+
module Repositories
|
26
|
+
module Git
|
27
|
+
class PackFileIndexLookupError < StandardError; end
|
28
|
+
##
|
29
|
+
# = PackFile
|
30
|
+
#
|
31
|
+
# Git uses it's "gc" command to pack loose objects into PackFiles.
|
32
|
+
# This is one file, preferably with an index (though not requiring one),
|
33
|
+
# which stores a number of objects in a very simple raw form.
|
34
|
+
#
|
35
|
+
# This class handles the index file, which is used for fast lookups of
|
36
|
+
# entries in packfiles. They use fanouts to make searching very simple.
|
37
|
+
#
|
38
|
+
# The fanout values are interesting - they are the number of entries with a first sha1
|
39
|
+
# byte less than or equal to the byte provided. So if the only sha1s you can
|
40
|
+
# find in a given packfile are 00112233.... and 020304.... then the first few
|
41
|
+
# fanouts are:
|
42
|
+
# fanout[0] = 1
|
43
|
+
# fanout[1] = 1
|
44
|
+
# fanout[2] = 2
|
45
|
+
# ....
|
46
|
+
# fanout[254] = 2
|
47
|
+
class PackFileIndex
|
48
|
+
|
49
|
+
class << self
|
50
|
+
##
|
51
|
+
# Initializes the index file by reading from an input source. Does not
|
52
|
+
# automatically read in any actual data, but instead finds the necessary
|
53
|
+
# values to make searching by SHA1 quick.
|
54
|
+
#
|
55
|
+
# @param [IO, #read] fp an input stream at the beginning of the index
|
56
|
+
def parse(fp)
|
57
|
+
fourcc = fp.read(4)
|
58
|
+
if fourcc == "\xfftOc"
|
59
|
+
version = fp.read(4).unpack("N").first
|
60
|
+
PackFileIndexV2.new(fp) if version == 2
|
61
|
+
else
|
62
|
+
fp.seek(-4, IO::SEEK_CUR)
|
63
|
+
PackFileIndexV1.new(fp)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
FANOUT_TABLE_SIZE = 4 * 256
|
69
|
+
|
70
|
+
# Common PackFileIndex methods
|
71
|
+
attr_reader :size
|
72
|
+
attr_reader :fanout
|
73
|
+
|
74
|
+
# Common initialization code
|
75
|
+
# @param [IO, #read] fp input stream to read the file from
|
76
|
+
def initialize(fp)
|
77
|
+
@fp = fp
|
78
|
+
@fanout = @fp.read(FANOUT_TABLE_SIZE).unpack("N256")
|
79
|
+
@size = @fanout[255]
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# uses fanout logic to determine the indices in which the desired
|
84
|
+
# hash might be found. This range can be searched to find the hash.
|
85
|
+
def search_range_for_hash(hash)
|
86
|
+
byte = Support::StringUtils.ord(hash[0,1])
|
87
|
+
min = byte > 0 ? (fanout[byte - 1]) : 0
|
88
|
+
max = fanout[byte]
|
89
|
+
min...max
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# This class is for the older version of index files. They are
|
95
|
+
# structured as simply a fanout table and a list of [offset][sha1] entries.
|
96
|
+
# There's a checksum at the bottom for verification.
|
97
|
+
#
|
98
|
+
# Initialization only reads in the fanout table, and the file is left open.
|
99
|
+
class PackFileIndexV1 < PackFileIndex
|
100
|
+
ENTRY_TABLE_START = 256 * 4
|
101
|
+
ENTRY_TABLE_ENTRY_SIZE = 20 + 4
|
102
|
+
ENTRY_TABLE_FORMAT = "Na20"
|
103
|
+
|
104
|
+
##
|
105
|
+
# Looks up the offset in a packfile of a given hash. nil is returned if
|
106
|
+
# the sha1 isn't found.
|
107
|
+
#
|
108
|
+
# @param [String] hsh a binary, sha-1 hash of the desired object
|
109
|
+
# @return [Fixnum, nil] the offset into the corresponding packfile at which you
|
110
|
+
# can find the object
|
111
|
+
def offset_for_hash(hsh)
|
112
|
+
range = search_range_for_hash hsh
|
113
|
+
@fp.seek(FANOUT_TABLE_START + range.begin * ENTRY_TABLE_ENTRY_SIZE, IO::SEEK_SET)
|
114
|
+
# linear search for now.
|
115
|
+
# TODO: binary search!
|
116
|
+
range.each do |_|
|
117
|
+
offset, sha1 = @fp.read(ENTRY_TABLE_ENTRY_SIZE).unpack(ENTRY_TABLE_FORMAT)
|
118
|
+
return offset if sha1 == hsh
|
119
|
+
end
|
120
|
+
raise PackFileIndexLookupError.new("Couldn't find the hash #{hsh.inspect} in the packfile index.")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# This class is for the older version of index files. They are
|
126
|
+
# structured as simply a fanout table and a list of [offset][sha1] entries.
|
127
|
+
# There's a checksum at the bottom for verification.
|
128
|
+
#
|
129
|
+
# Initialization only reads in the fanout table, and the file is left open.
|
130
|
+
class PackFileIndexV2 < PackFileIndex
|
131
|
+
ENTRY_TABLE_START = 256 * 4 + 8
|
132
|
+
SHA1_FORMAT = "a20"
|
133
|
+
SHA1_SIZE = 20
|
134
|
+
|
135
|
+
def sha1_table_offset
|
136
|
+
ENTRY_TABLE_START
|
137
|
+
end
|
138
|
+
|
139
|
+
def crc_table_offset
|
140
|
+
ENTRY_TABLE_START + size * SHA1_SIZE
|
141
|
+
end
|
142
|
+
|
143
|
+
def offset_table_offset
|
144
|
+
crc_table_offset + size * 4
|
145
|
+
end
|
146
|
+
|
147
|
+
def big_offset_table_offset
|
148
|
+
offset_table_offset + size * 4
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Looks up the offset in a packfile of a given hash. nil is returned if
|
153
|
+
# the sha1 isn't found.
|
154
|
+
#
|
155
|
+
# @param [String] hsh a binary, sha-1 hash of the desired object
|
156
|
+
# @return [Fixnum, nil] the offset into the corresponding packfile at which you
|
157
|
+
# can find the object
|
158
|
+
def offset_for_hash(hsh)
|
159
|
+
range = search_range_for_hash hsh
|
160
|
+
@fp.seek(sha1_table_offset + range.begin * SHA1_SIZE, IO::SEEK_SET)
|
161
|
+
# linear search for now.
|
162
|
+
# TODO: binary search!
|
163
|
+
range.each do |idx|
|
164
|
+
sha1 = @fp.read(SHA1_SIZE).unpack(SHA1_FORMAT).first # sha1s are 20 bytes
|
165
|
+
return offset_for_index(idx) if sha1 == hsh
|
166
|
+
end
|
167
|
+
raise PackFileIndexLookupError.new("Couldn't find the hash #{hsh.inspect} in the packfile index.")
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Looks up the offset of an object given its index in the file. This index
|
172
|
+
# is assumed to have been determined by matching the sha1 of the object to
|
173
|
+
# a sha1 in the SHA1 table of the index.
|
174
|
+
#
|
175
|
+
# This requires a little more advanced logic because there is an offset table,
|
176
|
+
# and a 64-bit offset table. The normal offset table only supports 31-bit offsets,
|
177
|
+
# if the high bit is set, then the 31-bit value is actually an index into the big
|
178
|
+
# offset table.
|
179
|
+
#
|
180
|
+
# @param [Integer] idx the index into the offset table
|
181
|
+
# @return [Integer] the offset in the packfile where you can find the object with
|
182
|
+
# the given index into the index
|
183
|
+
def offset_for_index(idx)
|
184
|
+
@fp.seek(offset_table_offset + 4 * idx, IO::SEEK_SET)
|
185
|
+
offset = @fp.read(4).unpack("N").first
|
186
|
+
if offset & 0x80000000 != 0
|
187
|
+
@fp.seek(big_offset_table_offset + 8 * (offset & 0x7fffffff))
|
188
|
+
offset = Support::EncodingUtils.network_to_host_64(@fp.read(8).unpack("Q").first)
|
189
|
+
end
|
190
|
+
offset
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
# This was written by reading the Git Book. No source code was
|
16
|
+
# examined to produce this code. It is the original work of its
|
17
|
+
# creators, Michael Edgar and Ari Brown.
|
18
|
+
#
|
19
|
+
# http://book.git-scm.com/7_how_git_stores_objects.html
|
20
|
+
|
21
|
+
module Amp
|
22
|
+
module Core
|
23
|
+
module Repositories
|
24
|
+
module Git
|
25
|
+
##
|
26
|
+
# = LooseObject
|
27
|
+
#
|
28
|
+
# A single loose object (tree, tag, commit, etc.) in the Git system.
|
29
|
+
# Its type and content will be determined after we read the file.
|
30
|
+
#
|
31
|
+
# It is uniquely identified by a SHA1 hash.
|
32
|
+
class RawObject
|
33
|
+
attr_reader :type, :content, :hash_id
|
34
|
+
AUTHOR_MATCH = /([^<]*) <(.*)> (\d+) ([-+]?\d+)/
|
35
|
+
class << self
|
36
|
+
|
37
|
+
|
38
|
+
def for_hash(hsh, git_opener)
|
39
|
+
# no way to handle packed objects yet
|
40
|
+
LooseObject.lookup(hsh, git_opener)
|
41
|
+
end
|
42
|
+
|
43
|
+
def construct(hsh, opener, type, content)
|
44
|
+
# type, content should both be set now
|
45
|
+
type_lookup = {'tree' => TreeObject, 'commit' => CommitObject, 'tag' => TagObject}
|
46
|
+
object_klass = type_lookup[type] || LooseObject
|
47
|
+
result = object_klass.new(hsh, opener, content)
|
48
|
+
result.type = type if object_klass == LooseObject
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
module Amp
|
16
|
+
module Core
|
17
|
+
module Repositories
|
18
|
+
module Git
|
19
|
+
class StagingArea < Amp::Core::Repositories::AbstractStagingArea
|
20
|
+
|
21
|
+
attr_accessor :repo
|
22
|
+
|
23
|
+
def initialize(repo)
|
24
|
+
@repo = repo
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Marks a file to be added to the repository upon the next commit.
|
29
|
+
#
|
30
|
+
# @param [[String]] filenames a list of files to add in the next commit
|
31
|
+
# @return [Boolean] true for success, false for failure
|
32
|
+
def add(*filenames)
|
33
|
+
`git add #{filenames.join ' '} 2> /dev/null`
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# The directory used by the VCS to store magical information (.hg, .git, etc.).
|
39
|
+
#
|
40
|
+
# @api
|
41
|
+
# @return [String] relative to root
|
42
|
+
def vcs_dir
|
43
|
+
'.hg'
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Marks a file to be removed from the repository upon the next commit. Last argument
|
48
|
+
# can be a hash, which can take an :unlink key, specifying whether the files should actually
|
49
|
+
# be removed or not.
|
50
|
+
#
|
51
|
+
# @param [String, Array<String>] filenames a list of files to remove in the next commit
|
52
|
+
# @return [Boolean] true for success, false for failure
|
53
|
+
def remove(*filenames)
|
54
|
+
`git rm #{filenames.join ' '} 2> /dev/null`
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Set +file+ as normal and clean. Un-removes any files marked as removed, and
|
60
|
+
# un-adds any files marked as added.
|
61
|
+
#
|
62
|
+
# @param [String, Array<String>] files the name of the files to mark as normal
|
63
|
+
# @return [Boolean] success marker
|
64
|
+
def normal(*files)
|
65
|
+
# Do nothing...
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Mark the files as untracked.
|
71
|
+
#
|
72
|
+
# @param [Array<String>] files the name of the files to mark as untracked
|
73
|
+
# @return [Boolean] success marker
|
74
|
+
def forget(*files)
|
75
|
+
`git rm --cached #{files.join ' '} 2> /dev/null`
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Marks a file to be copied from the +from+ location to the +to+ location
|
81
|
+
# in the next commit, while retaining history.
|
82
|
+
#
|
83
|
+
# @param [String] from the source of the file copy
|
84
|
+
# @param [String] to the destination of the file copy
|
85
|
+
# @return [Boolean] true for success, false for failure
|
86
|
+
def copy(from, to)
|
87
|
+
`git cp #{from} #{to} 2> /dev/null`
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Marks a file to be moved from the +from+ location to the +to+ location
|
93
|
+
# in the next commit, while retaining history.
|
94
|
+
#
|
95
|
+
# @param [String] from the source of the file move
|
96
|
+
# @param [String] to the destination of the file move
|
97
|
+
# @return [Boolean] true for success, false for failure
|
98
|
+
def move(from, to)
|
99
|
+
`git mv #{from} #{to} 2> /dev/null`
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Marks a modified file to be included in the next commit.
|
105
|
+
# If your VCS does this implicitly, this should be defined as a no-op.
|
106
|
+
#
|
107
|
+
# @param [String, Array<String>] filenames a list of files to include for committing
|
108
|
+
# @return [Boolean] true for success, false for failure
|
109
|
+
def include(*filenames)
|
110
|
+
add filenames
|
111
|
+
end
|
112
|
+
alias_method :stage, :include
|
113
|
+
|
114
|
+
##
|
115
|
+
# Mark a modified file to not be included in the next commit.
|
116
|
+
# If your VCS does not include this idea because staging a file is implicit, this should
|
117
|
+
# be defined as a no-op.
|
118
|
+
#
|
119
|
+
# @param [[String]] filenames a list of files to remove from the staging area for committing
|
120
|
+
# @return [Boolean] true for success, false for failure
|
121
|
+
def exclude(*filenames)
|
122
|
+
`git rm --cached #{filenames.join ' '} 2> /dev/null`
|
123
|
+
true
|
124
|
+
end
|
125
|
+
alias_method :unstage, :exclude
|
126
|
+
|
127
|
+
##
|
128
|
+
# Returns a Symbol.
|
129
|
+
#
|
130
|
+
# If you call localrepo#status from this method... well...
|
131
|
+
# I DARE YOU!
|
132
|
+
def file_status(filename)
|
133
|
+
parse!
|
134
|
+
inverted = @status.inject({}) do |h, (k, v)|
|
135
|
+
v.each {|v_| h[v_] = k }
|
136
|
+
h
|
137
|
+
end
|
138
|
+
|
139
|
+
# lame hack, i know
|
140
|
+
case val = inverted[filename]
|
141
|
+
when :modified
|
142
|
+
:normal
|
143
|
+
else
|
144
|
+
val
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# modified, lookup, or clean
|
149
|
+
# in this case, since we're shelling out,
|
150
|
+
# only modified or clean
|
151
|
+
def file_precise_status(filename, st)
|
152
|
+
parse!
|
153
|
+
inverted = @status.inject({}) do |h, (k, v)|
|
154
|
+
v.each {|v_| h[v_] = k }
|
155
|
+
h
|
156
|
+
end
|
157
|
+
|
158
|
+
# bleh this code sucks
|
159
|
+
if inverted[filename] == :modified
|
160
|
+
:modified
|
161
|
+
else
|
162
|
+
:clean
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Calculates the difference (in bytes) between a file and its last tracked state.
|
168
|
+
#
|
169
|
+
# Supplements the built-in #status method so that its output will include deltas.
|
170
|
+
#
|
171
|
+
# @apioptional
|
172
|
+
# @param [String] file the filename to look up
|
173
|
+
# @param [File::Stats] st the current results of File.lstat(file)
|
174
|
+
# @return [Fixnum] the number of bytes difference between the file and
|
175
|
+
# its last tracked state.
|
176
|
+
def calculate_delta(file, st)
|
177
|
+
0
|
178
|
+
end
|
179
|
+
|
180
|
+
def parse!
|
181
|
+
return if @parsed
|
182
|
+
|
183
|
+
@status = {}
|
184
|
+
data = `git status 2> /dev/null`.split("\n")
|
185
|
+
data.inject @status do |h, line|
|
186
|
+
case line
|
187
|
+
when /^#\s+(\w+):\s(.+)$/
|
188
|
+
h[$1.to_sym] = $2.strip
|
189
|
+
when /^#\s+new file:\s(.+)$/
|
190
|
+
h[:added] = $1.strip
|
191
|
+
when /^#\s+([^ ]+)$/
|
192
|
+
h[:untracked] = $1.strip
|
193
|
+
else
|
194
|
+
h
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
@parsed = true
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Returns all files tracked by the repository *for the working directory* - not
|
203
|
+
# to be confused with the most recent changeset.
|
204
|
+
#
|
205
|
+
# @api
|
206
|
+
# @return [Array<String>] all files tracked by the repository at this moment in
|
207
|
+
# time, including just-added files (for example) that haven't been committed yet.
|
208
|
+
def all_files
|
209
|
+
Amp::Git::WorkingDirectoryChangeset.new(@repo).all_files
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
# This was written by reading the Git Book. No source code was
|
16
|
+
# examined to produce this code. It is the original work of its
|
17
|
+
# creators, Michael Edgar and Ari Brown.
|
18
|
+
#
|
19
|
+
# http://book.git-scm.com/7_how_git_stores_objects.html
|
20
|
+
|
21
|
+
module Amp
|
22
|
+
module Core
|
23
|
+
module Repositories
|
24
|
+
module Git
|
25
|
+
##
|
26
|
+
# = TagObject
|
27
|
+
#
|
28
|
+
# This is a formal tag object in the commit system. Most tags actually don't
|
29
|
+
# involve one of these objects - they just create a ref (alias) to a commit
|
30
|
+
# object. This tag will also reference a (usually) commit object, but they
|
31
|
+
# can contain their own messages, including PGP signatures. And honestly,
|
32
|
+
# we just have to suck it up and support them.
|
33
|
+
class TagObject < RawObject
|
34
|
+
attr_reader :object_ref, :reffed_type, :tag, :tagger, :date, :message
|
35
|
+
|
36
|
+
##
|
37
|
+
# Initializes the TagObject. Needs a hash to identify it and
|
38
|
+
# an opener. The opener should point to the .git directory. Immediately
|
39
|
+
# parses the object.
|
40
|
+
#
|
41
|
+
# @param [String] hsh the hash to use to find the object
|
42
|
+
# @param [Support::RootedOpener] opener the opener to use to open the
|
43
|
+
# object file
|
44
|
+
# @param [String] content if the content is known already, use
|
45
|
+
# the provided content instead
|
46
|
+
def initialize(hsh, opener, content = nil)
|
47
|
+
if content
|
48
|
+
@hash_id, @opener = hsh, opener
|
49
|
+
@type = 'tag'
|
50
|
+
@content = content
|
51
|
+
else
|
52
|
+
super(hsh, opener)
|
53
|
+
end
|
54
|
+
parse!
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
##
|
60
|
+
# Parses the commit object into our attributes.
|
61
|
+
def parse!
|
62
|
+
lines = @content.split("\n")
|
63
|
+
last_idx = 0
|
64
|
+
lines.each_with_index do |line, idx|
|
65
|
+
case line
|
66
|
+
when /^object (.{40})/
|
67
|
+
@object_ref = Support::StringUtils.unhexlify($1)
|
68
|
+
when /^type (\S+)/
|
69
|
+
@reffed_type = $1
|
70
|
+
when /^tag\s+(.*)\s*$/
|
71
|
+
@tag = $1
|
72
|
+
when /^tagger #{AUTHOR_MATCH}/
|
73
|
+
@tagger = "#{$1} <#{$2}>"
|
74
|
+
@date = Time.at($3.to_i)
|
75
|
+
when ""
|
76
|
+
last_idx = idx + 1
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@message = lines[last_idx..-1].join("\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
# This was written by reading the Git Book. No source code was
|
16
|
+
# examined to produce this code. It is the original work of its
|
17
|
+
# creators, Michael Edgar and Ari Brown.
|
18
|
+
#
|
19
|
+
# http://book.git-scm.com/7_how_git_stores_objects.html
|
20
|
+
|
21
|
+
module Amp
|
22
|
+
module Core
|
23
|
+
module Repositories
|
24
|
+
module Git
|
25
|
+
##
|
26
|
+
# = TreeObject
|
27
|
+
#
|
28
|
+
# This is a tree object representing a versioned directory. It has
|
29
|
+
# a list of
|
30
|
+
class TreeObject < RawObject
|
31
|
+
TreeEntry = Struct.new(:name, :mode, :ref)
|
32
|
+
|
33
|
+
##
|
34
|
+
# Initializes the RawObject. Needs a hash to identify it and
|
35
|
+
# an opener. The opener should point to the .git directory.
|
36
|
+
#
|
37
|
+
# @param [String] hsh the hash to use to find the object
|
38
|
+
# @param [Support::RootedOpener] opener the opener to use to open the
|
39
|
+
# object file
|
40
|
+
# @param [String] content if the content is known already, use
|
41
|
+
# the provided content instead
|
42
|
+
def initialize(hsh, opener, content = nil)
|
43
|
+
if content
|
44
|
+
@hash_id, @opener = hsh, opener
|
45
|
+
@type = 'tree'
|
46
|
+
@content = content
|
47
|
+
else
|
48
|
+
super(hsh, opener)
|
49
|
+
end
|
50
|
+
parse!
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Returns a list of the names of the files/directories (blobs/trees) in the
|
55
|
+
# tree
|
56
|
+
#
|
57
|
+
# @return [Array<String>] a (possibly unsorted) list of file and
|
58
|
+
# and directory names in this tree
|
59
|
+
def entry_names
|
60
|
+
@pairs.keys
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Returns the number of entries in the directory.
|
65
|
+
#
|
66
|
+
# @return [Fixnum] the number of entries in this directory
|
67
|
+
def size
|
68
|
+
@pairs.size
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Returns a TreeObject for the given filename in this level of
|
73
|
+
# the tree.
|
74
|
+
#
|
75
|
+
# @param [String] name the name of the file to look up
|
76
|
+
# @return [TreeEntry] the treeobject representing the file
|
77
|
+
def tree_lookup(name)
|
78
|
+
@pairs[name]
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def parse!
|
84
|
+
require 'strscan'
|
85
|
+
@pairs = {}
|
86
|
+
scanner = StringScanner.new(@content)
|
87
|
+
until scanner.eos?
|
88
|
+
break unless scanner.scan(/(\d+) (\S+?)\x00(.{20})/m)
|
89
|
+
new_entry = TreeEntry.new(scanner[2], scanner[1].to_i(8), scanner[3])
|
90
|
+
@pairs[new_entry.name] = new_entry
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|