amp-git 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|