tvc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "json", ">= 0"
6
+ gem "diff-lcs", ">= 0"
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "shoulda", ">= 0"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.5.2"
13
+ gem "rcov", ">= 0"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.2)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ json (1.5.1-x86-mingw32)
11
+ rake (0.8.7)
12
+ rcov (0.9.9)
13
+ shoulda (2.11.3)
14
+
15
+ PLATFORMS
16
+ x86-mingw32
17
+
18
+ DEPENDENCIES
19
+ bundler (~> 1.0.0)
20
+ diff-lcs
21
+ jeweler (~> 1.5.2)
22
+ json
23
+ rcov
24
+ shoulda
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Chris O'Neal
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,86 @@
1
+ = TVC - Terrible Version Control
2
+
3
+ == What TVC Is:
4
+
5
+ TVC is a really terrible pseudo-remake of git in Ruby. It should never really be used; its creation was a joke and a learning experience. It will always be inferior to git. I read a blog post called [The Git Parable](http://tom.preston-werner.com/2009/05/19/the-git-parable.html), and wrote it based on some of the explanations given there. It's not a terribly well written piece of code, just a script I hacked together in the span of about a day. I'm slowly adding to and revising it, but don't expect much. If you're the version control police, please don't arrest me. It was all meant as good fun, I swear!
6
+
7
+ Also, this is the first real thing I've written in Ruby. It's not going to be pretty, and I'm pretty sure I'm not doing things "the Ruby way" or whatever. Be gentle.
8
+
9
+ However, you might find it interesting to look at. It works in limited situations: if you only want a local repository, and you don't need a whole lot of features or quality, well, it might do.
10
+
11
+ == Features (if you can call them that):
12
+
13
+ * Storing versions
14
+ * Branching
15
+ * Merging (but it's a pretty terrible merge right now, don't trust it!)
16
+ * Viewing a list of commits for the branch you're on
17
+ * Pulling a previous revision
18
+
19
+ == What You Need:
20
+
21
+ * Ruby 1.9
22
+ * The desire to use sub-par code
23
+
24
+ == What To Do:
25
+
26
+ Well, if you're still reading after all my warnings, I guess you want to try this out. You're crazy.
27
+
28
+ First, install the TVC gem.
29
+
30
+ % gem install tvc
31
+
32
+ Go to where you want your repository and type:
33
+
34
+ % tvc init
35
+
36
+ This will initialize the repository. After that, commit like so:
37
+
38
+ % tvc commit "some message about committing"
39
+
40
+ And of course, it's going to be the same anytime you want to commit changes to the repository. To branch, type:
41
+
42
+ % tvc branch <some name for the branch>
43
+
44
+ If no name is specified, it will list all current branches. To move to a branch, type:
45
+
46
+ % tvc checkout <name of a branch>
47
+
48
+ Now you're on that branch, ready to modify it. Once you've committed changes to the branch and want to merge them in somewhere else (say, for instance, the master branch), type:
49
+
50
+ % tvc checkout <branch you want to merge into>
51
+ % tvc merge <branch you want to merge from>
52
+
53
+ By now, you've noticed each commit has a corresponding SHA-2 hash. If you want to pull the files from a revision, type:
54
+
55
+ % tvc replace <the hash of the commit you want>
56
+
57
+ You don't have to type the whole hash, you can type just a few characters off the front of it. You run the risk of messing up, I suppose. Be careful. If you want to roll back your repository to how it was at a specific commit, type:
58
+
59
+ % tvc rollback <the hash of the commit you want>
60
+
61
+ And if you want to just reset everything to how it was before you screwed everything up, you can go back to the last commit by typing:
62
+
63
+ % tvc reset
64
+
65
+ To get a list of all commits up the tree from where you currently are, type:
66
+
67
+ % tvc history
68
+
69
+ To see a list of what has changed since the last commit to the repository, type:
70
+
71
+ % tvc status
72
+
73
+ And, if you ever forget these few commands, they're accessible by typing:
74
+
75
+ % tvc
76
+
77
+ or
78
+
79
+ % tvc help
80
+
81
+
82
+ == Things That Might Happen Eventually:
83
+
84
+ * Better status information.
85
+ * Unit tests. Probably should have started with those, but oh well.
86
+ * The ability to work with external repositories (push, pull, users, blah blah blah)
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "tvc"
16
+ gem.homepage = "http://github.com/ctoneal/tvc"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{TVC - Terrible Version Control}
19
+ gem.description = %Q{TVC is an awful git clone that should never be used, ever.}
20
+ gem.email = "ctoneal@gmail.com"
21
+ gem.authors = ["Chris O'Neal"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ #gem.add_dependency = "json", ">= 0"
25
+ #gem.add_dependency = "diff-lcs", ">= 0"
26
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
27
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ require 'rcov/rcovtask'
39
+ Rcov::RcovTask.new do |test|
40
+ test.libs << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ end
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "tvc #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/tvc ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'tvc'
4
+
5
+ TVC.new(ARGV)
data/lib/tvc.rb ADDED
@@ -0,0 +1,635 @@
1
+ require 'rubygems'
2
+ require 'digest/sha2'
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'tmpdir'
6
+ require 'diff/lcs'
7
+ require 'zlib'
8
+
9
+ class Diff::LCS::Change
10
+ attr_accessor :position
11
+ end
12
+
13
+ class TVC
14
+ # print an error that states that the program must be run in the directory
15
+ # with the repository, and that the repository needs to be initialized.
16
+ #
17
+ # this was a commonly used error, hence the function just for it.
18
+ def repositoryIssueError
19
+ puts "The repository either has not been initialized
20
+ or this command is not being run from the base
21
+ directory of the repository".gsub(/\s+/, " ").strip
22
+ end
23
+
24
+ # initialize a repository
25
+ def init
26
+ Dir.chdir(@runDir)
27
+ Dir.mkdir(".tvc") unless Dir::exists?(".tvc")
28
+ @repoDir = getRepositoryDirectory
29
+ Dir.mkdir(getObjectsDirectory) unless Dir::exists?(getObjectsDirectory)
30
+ saveDataToJson(File.join(@repoDir, "pointers"), [{"name" => "master", "hash" => nil, "parent" => nil}])
31
+ saveDataToJson(File.join(@repoDir, "history"), [])
32
+ saveDataToJson(File.join(@repoDir, "current"), [{"name" => "master", "hash" => nil, "parent" => nil}])
33
+ end
34
+
35
+ # commit current changes
36
+ def commit(message)
37
+ hash = createObjects(@runDir)
38
+ current = getCurrentEntry
39
+ data = {"message" => message, "hash" => hash, "parent" => current["hash"]}
40
+ addHistoryEntry(data)
41
+ current["parent"] = data["parent"]
42
+ current["hash"] = data["hash"]
43
+ changeCurrentEntry(current)
44
+ changeBranchHash(current["name"], current["hash"])
45
+ puts hash
46
+ puts message
47
+ end
48
+
49
+ # replace current files with requested version
50
+ def replace(versionHash)
51
+ hash = getFullHash(versionHash)
52
+ if not hash.nil?
53
+ deleteFiles(@runDir)
54
+ rootInfo = getDataFromJson(File.join(getObjectsDirectory, hash))
55
+ moveFiles(@runDir, rootInfo)
56
+ else
57
+ puts "This version does not exist"
58
+ return
59
+ end
60
+ end
61
+
62
+ # prints a list of commits up the chain
63
+ def history
64
+ current = getCurrentEntry
65
+ hash = current["hash"]
66
+ versions = getHistoryEntries
67
+ while not hash.nil?
68
+ versions.each do |version|
69
+ if version["hash"] == hash
70
+ puts "#{version["hash"]}\n#{version["message"]}\n\n"
71
+ hash = version["parent"]
72
+ break
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ # create a branch
79
+ def branch(name)
80
+ b = findBranch(name)
81
+ if b.nil?
82
+ current = getCurrentEntry
83
+ newBranch = {"name" => name, "hash" => current["hash"], "parent" => current["hash"]}
84
+ addBranchEntry(newBranch)
85
+ else
86
+ puts "Branch already exists"
87
+ end
88
+ end
89
+
90
+ # checkout a branch
91
+ def checkout(branchName)
92
+ checkoutBranch = findBranch(branchName)
93
+ if not checkoutBranch.nil?
94
+ changeCurrentEntry(checkoutBranch)
95
+ replace(checkoutBranch["hash"])
96
+ else
97
+ puts "Branch does not exist"
98
+ return
99
+ end
100
+ end
101
+
102
+ # merges a branch into the current branch
103
+ # i'm betting this is going to look like crap
104
+ def merge(branchName)
105
+ source = findBranch(branchName)
106
+ target = getCurrentEntry
107
+ parent = findCommonAncestor(source, target)
108
+ if not source.nil?
109
+ if not parent.nil?
110
+ mergeFilesForDirectory(@runDir, source["hash"], target["hash"], parent["hash"])
111
+ commit("Merged branch #{branchName}")
112
+ else
113
+ puts "Could not find common ancestor"
114
+ end
115
+ else
116
+ puts "Branch doesn't exist"
117
+ end
118
+ end
119
+
120
+ # find and display the changes from the current version in the repository
121
+ def status
122
+ current = getCurrentEntry
123
+ changes = findChanges(@runDir, current["hash"])
124
+ if changes.empty?
125
+ puts "No changes made"
126
+ else
127
+ changes.each do |change|
128
+ puts "#{change["name"]} #{change["type"]}"
129
+ end
130
+ end
131
+ end
132
+
133
+ # creates a list of changes made
134
+ def findChanges(directory, currentHash)
135
+ currentData = getDataFromJson(File.join(getObjectsDirectory, currentHash))
136
+ changes = []
137
+ # loop through every item in the directory
138
+ Dir.foreach(directory) do |dirItem|
139
+ if dirItem != '.' && dirItem != '..' && dirItem != ".tvc"
140
+ dirPath = File.join(directory, dirItem)
141
+ foundItem = nil
142
+ itemType = File.directory?(dirPath) ? "tree" : "blob"
143
+ # compare the item to the directory's json
144
+ currentData.each_index do |index|
145
+ currentItem = currentData[index]
146
+ if currentItem["name"] == dirItem && currentItem["type"] == itemType
147
+ foundItem = currentItem
148
+ currentData.delete_at(index)
149
+ break
150
+ end
151
+ end
152
+ # if nothing in the json matched this item, it must be new
153
+ if foundItem.nil?
154
+ newChange = { "type" => "added", "name" => dirPath.gsub(@runDir, "") }
155
+ changes << newChange
156
+ if itemType == "tree"
157
+ newChanges = findChangesInNewDirectory(dirPath)
158
+ newChanges.each do |change|
159
+ changes << change
160
+ end
161
+ end
162
+ # we found a match, so we need to compare it
163
+ else
164
+ # if it's a directory, continue down to find changes in there
165
+ if itemType == "tree"
166
+ newChanges = findChanges(dirPath, foundItem["hash"])
167
+ newChanges.each do |newChange|
168
+ changes << newChange
169
+ end
170
+ # if it's a file, compare the contents
171
+ else
172
+ # this is a really hacky way to do this, but i'm so lazy
173
+ repoData = getFileData(File.join(getObjectsDirectory, foundItem["hash"]))
174
+ repoData = repoData.gsub(/\s/, "")
175
+ fileData = File.open(dirPath) { |f| f.read }
176
+ fileData = fileData.gsub(/\s/, "")
177
+ diffs = Diff::LCS.diff(repoData, fileData)
178
+ modified = false
179
+ diffs.each do |diffArray|
180
+ diffArray.each do |diff|
181
+ if diff.action != "=" && diff.element != ""
182
+ modified = true;
183
+ end
184
+ end
185
+ end
186
+ if modified
187
+ newChange = { "type" => "modified", "name" => dirPath.gsub(@runDir, "") }
188
+ changes << newChange
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ # if we still have items in the directory json, they must have been deleted
195
+ currentData.each do |currentItem|
196
+ newChange = { "type" => "deleted", "name" => currentItem["name"] }
197
+ changes << newChange
198
+ end
199
+ return changes
200
+ end
201
+
202
+ # returns a list of everything in this directory.
203
+ def findChangesInNewDirectory(directory)
204
+ changes = []
205
+ Dir.foreach(directory) do |dirItem|
206
+ if dirItem != '.' && dirItem != '..' && dirItem != ".tvc"
207
+ dirPath = File.join(directory, dirItem)
208
+ newChange = { "type" => "added", "name" => dirPath.gsub(@runDir, "") }
209
+ changes << newChange
210
+ if File.directory?(dirPath)
211
+ newChanges = findChangesInNewDirectory(dirPath)
212
+ newChanges.each do |change|
213
+ changes << change
214
+ end
215
+ end
216
+ end
217
+ end
218
+ return changes
219
+ end
220
+
221
+ # attempts to find a common parent for the two revisions
222
+ def findCommonAncestor(source, target)
223
+ sourceParentHash = source["hash"]
224
+ versions = getHistoryEntries
225
+ # loop through until we hit the end of the tree for the source
226
+ while not sourceParentHash.nil?
227
+ sourceParent = nil
228
+ targetParentHash = target["hash"]
229
+ versions.each do |version|
230
+ if version["hash"] == sourceParentHash
231
+ sourceParent = version
232
+ break
233
+ end
234
+ end
235
+ # loop through the target tree, hoping we find something that matches up
236
+ while not targetParentHash.nil?
237
+ versions.each do |version|
238
+ if version["hash"] == targetParentHash
239
+ if targetParentHash == sourceParentHash
240
+ return version
241
+ else
242
+ targetParentHash = version["parent"]
243
+ end
244
+ end
245
+ end
246
+ end
247
+ if sourceParent.nil?
248
+ sourceParentHash = nil
249
+ else
250
+ sourceParentHash = sourceParent["parent"]
251
+ end
252
+ end
253
+ return nil
254
+ end
255
+
256
+ # attempts to merge the items for the specified directory together
257
+ def mergeFilesForDirectory(directory, sourceHash, targetHash, parentHash)
258
+ sourceJson = getDataFromJson(File.join(getObjectsDirectory, sourceHash))
259
+ targetJson = getDataFromJson(File.join(getObjectsDirectory, targetHash))
260
+ parentJson = getDataFromJson(File.join(getObjectsDirectory, parentHash))
261
+ # for each item in the source, attempt to find a corresponding item in the target
262
+ sourceJson.each do |sourceItem|
263
+ matchingItem = nil
264
+ parentMatch = nil
265
+ targetJson.each do |targetItem|
266
+ if targetItem["name"] == sourceItem["name"] && targetItem["type"] == sourceItem["type"]
267
+ matchingItem = targetItem
268
+ break
269
+ end
270
+ end
271
+ parentJson.each do |parentItem|
272
+ if parentItem["name"] == sourceItem["name"] && parentItem["type"] == sourceItem["type"]
273
+ parentMatch = parentItem
274
+ break
275
+ end
276
+ end
277
+ # we're only going to attempt to merge if we've found some common parent
278
+ # otherwise, we're just going to straight up replace that thing
279
+ if parentMatch.nil?
280
+ puts "No common ancestor found, pushing change to target"
281
+ createItem(directory, sourceItem)
282
+ # if there's no matching item, but there is a parent, well, i guess it got deleted
283
+ # in the target, but is needed by source. add it back in.
284
+ elsif matchingItem.nil?
285
+ puts "No matching item found, pushing change to target"
286
+ createItem(directory, sourceItem)
287
+ # if we've got everything we need, we'll attempt to merge
288
+ else
289
+ # if this is a tree, continue on to do all the logic for it
290
+ if sourceItem["type"] == "tree"
291
+ mergeFilesForDirectory(File.join(directory, sourceItem["name"]), sourceItem["hash"], matchingItem["hash"], parentMatch["hash"])
292
+ # if it's a fine, try to merge the two together
293
+ # man, this might fail horribly with binary files.
294
+ # watch out for that
295
+ elsif sourceItem["type"] == "blob"
296
+ if sourceItem["hash"] != matchingItem["hash"]
297
+ mergeFiles(sourceItem["hash"], matchingItem["hash"], parentMatch["hash"], File.join(directory, sourceItem["name"]))
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ # attempt to merge two files together given a common parent
305
+ # this seems like a pretty naive way of doing things, but i'm lazy!
306
+ def mergeFiles(sourceHash, targetHash, parentHash, targetPath)
307
+ sourceData = getFileData(File.join(getObjectsDirectory, sourceHash))
308
+ targetData = getFileData(File.join(getObjectsDirectory, targetHash))
309
+ parentData = getFileData(File.join(getObjectsDirectory, parentHash))
310
+ # get the changes it took to get from the parent to the source
311
+ sourceDiffs = Diff::LCS.diff(parentData, sourceData)
312
+ targetDiffs = Diff::LCS.diff(parentData, targetData)
313
+ # need to modify the source diffs based on the changes between the parent
314
+ # and the target.
315
+ # (this is really hacky and inefficient. i need to come up with a better
316
+ # way to do this)
317
+ targetDiffs.each do |targetDiffArray|
318
+ targetDiffArray.each do |targetDiff|
319
+ sourceDiffs.each do |sourceDiffArray|
320
+ sourceDiffArray.each do |sourceDiff|
321
+ if targetDiff.position <= sourceDiff.position
322
+ if targetDiff.action == "-"
323
+ sourceDiff.position -= targetDiff.element.length
324
+ elsif targetDiff.action == "+"
325
+ sourceDiff.position += targetDiff.element.length
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+ # once we have all the changes between the source and parent, and they've
333
+ # been adjusted by the changes between target and parent, apply the source
334
+ # changes to the target
335
+ mergedData = Diff::LCS.patch!(targetData, sourceDiffs)
336
+ mergedFile = File.open(targetPath, "w")
337
+ mergedFile.write mergedData
338
+ mergedFile.close
339
+ end
340
+
341
+ # attempt to create the item specified in the entry
342
+ def createItem(directory, entry)
343
+ # each "tree" item specifies a directory that should be made corresponding to
344
+ # that directory's json file (hash)
345
+ if entry["type"] == "tree"
346
+ d = directory + '/' + entry["name"]
347
+ Dir.mkdir(d)
348
+ dirJson = getDataFromJson(File.join(getObjectsDirectory, entry["hash"]))
349
+ moveFiles(d, dirJson)
350
+ # copy the item out of the objects folder into its proper place
351
+ elsif entry["type"] == "blob"
352
+ f = File.open(File.join(directory, entry["name"]), "w")
353
+ f.write(getFileData(File.join(getObjectsDirectory, entry["hash"])))
354
+ f.close
355
+ end
356
+ end
357
+
358
+ # prints a list of valid functions and their uses
359
+ def help
360
+ puts "help - This text"
361
+ puts "init - Initialize a repository"
362
+ puts "commit - Commit changes to a repository"
363
+ puts "branch - Create a new branch. If not given a branch name, it lists all current branches."
364
+ puts "checkout - Move to the specified branch for modifying"
365
+ puts "replace - Pulls the desired revision down from the repository"
366
+ puts "merge - Merges the specified branch with the current branch"
367
+ puts "status - Prints a list of changes from the current version of the repository"
368
+ end
369
+
370
+ # extracts json data from a given file
371
+ def getDataFromJson(filePath)
372
+ JSON.parse(getFileData(filePath))
373
+ end
374
+
375
+ # gets and decompresses a given file
376
+ def getFileData(filePath)
377
+ decompress(File.open(filePath, "rb") { |f| f.read })
378
+ end
379
+
380
+ # decompresses given data
381
+ def decompress(data)
382
+ Zlib::Inflate.inflate(data)
383
+ end
384
+
385
+ # compresses and saves data to a path
386
+ def saveFileData(filePath, data)
387
+ f = File.open(filePath, "wb")
388
+ f.write(compress(data))
389
+ f.close
390
+ end
391
+
392
+ # compresses given data
393
+ def compress(data)
394
+ Zlib::Deflate.deflate(data)
395
+ end
396
+
397
+ # puts data in json format in a given file
398
+ # warning: this will replace all text in the file
399
+ def saveDataToJson(filePath, data)
400
+ saveFileData(filePath, JSON.generate(data))
401
+ end
402
+
403
+ # changes the hash for a specified branch, and sets the parent to the previous hash
404
+ def changeBranchHash(name, hash)
405
+ b = findBranch(name)
406
+ branches = getBranches
407
+ branches.each do |branch|
408
+ if branch == b
409
+ branch["parent"] = branch["hash"]
410
+ branch["hash"] = hash
411
+ saveBranches(branches)
412
+ return
413
+ end
414
+ end
415
+ end
416
+
417
+ # returns the path to the objects directory
418
+ def getObjectsDirectory
419
+ return @repoDir + '/' + "objects"
420
+ end
421
+
422
+ # adds an entry to the history file
423
+ def addHistoryEntry(entry)
424
+ versions = getHistoryEntries
425
+ versions << entry
426
+ saveDataToJson(File.join(@repoDir, "history"), versions)
427
+ end
428
+
429
+ # retrieves the history entries
430
+ def getHistoryEntries
431
+ history = getDataFromJson(File.join(@repoDir, "history"))
432
+ end
433
+
434
+ # change the entry in the current file
435
+ def changeCurrentEntry(entry)
436
+ newEntry = [entry]
437
+ saveDataToJson(File.join(@repoDir, "current"), newEntry)
438
+ end
439
+
440
+ # get the entry in the current file
441
+ def getCurrentEntry
442
+ current = (getDataFromJson(File.join(@repoDir, "current")))[0]
443
+ end
444
+
445
+ # get all branches
446
+ def getBranches
447
+ branches = getDataFromJson(File.join(@repoDir, "pointers"))
448
+ end
449
+
450
+ # find a branch. nil if it does not exist
451
+ def findBranch(name)
452
+ branches = getBranches
453
+ branches.each do |branch|
454
+ if branch["name"] == name
455
+ return branch
456
+ end
457
+ end
458
+ return nil
459
+ end
460
+
461
+ # gets a full hash if only given a short hash.
462
+ # allows for easier specification of revisions,
463
+ # but can be kind of dangerous if the short hash is too short
464
+ def getFullHash(hash)
465
+ Dir.foreach(getObjectsDirectory) do |dir|
466
+ dirpath = getObjectsDirectory + '/' + dir
467
+ unless File.directory?(dirpath)
468
+ if dir[0, hash.length] == hash
469
+ return dir
470
+ end
471
+ end
472
+ end
473
+ return nil
474
+ end
475
+
476
+ # recursively deletes all files in the directory.
477
+ def deleteFiles(directoryName)
478
+ Dir.foreach(directoryName) do |dir|
479
+ dirpath = directoryName + '/' + dir
480
+ if File.directory?(dirpath)
481
+ if dir != '.' && dir != '..' && dir != ".tvc"
482
+ deleteFiles(dirpath)
483
+ Dir.delete(dirpath)
484
+ end
485
+ else
486
+ File.delete(dirpath)
487
+ end
488
+ end
489
+ end
490
+
491
+ # gets files from the given json
492
+ def moveFiles(directoryName, jsonInfo)
493
+ jsonInfo.each do |item|
494
+ createItem(directoryName, item)
495
+ end
496
+ end
497
+
498
+ # save objects in the objects folder, based on hash
499
+ # create json files to index them
500
+ def createObjects(directoryName)
501
+ objects = []
502
+ Dir.foreach(directoryName) do |dir|
503
+ dirpath = directoryName + '/' + dir
504
+ # go down into each valid directory and make objects for its contents
505
+ if File.directory?(dirpath)
506
+ if dir != '.' && dir != '..' && dir != ".tvc"
507
+ hash = createObjects(dirpath)
508
+ data = { "type" => "tree", "name" => dir, "hash" => hash }
509
+ objects << data
510
+ end
511
+ # create a hash based on the file contents
512
+ else
513
+ fileData = File.open(dirpath, "rb") { |f| f.read }
514
+ tempFileName = File.join(@repoDir, "temp")
515
+ saveFileData(tempFileName, fileData)
516
+ hash = createHash(tempFileName)
517
+ data = { "type" => "blob", "name" => dir, "hash" => hash }
518
+ FileUtils.cp(tempFileName, File.join(getObjectsDirectory, hash))
519
+ File.delete(tempFileName)
520
+ objects << data
521
+ end
522
+ end
523
+ # save off objects json file to a temp file
524
+ tempFileName = File.join(@repoDir, "temp")
525
+ saveDataToJson(tempFileName, objects)
526
+ # get the hash for the temp file, save it as that, and return the hash
527
+ hash = createHash(tempFileName)
528
+ FileUtils.cp(tempFileName, File.join(getObjectsDirectory, hash))
529
+ File.delete(tempFileName)
530
+ return hash
531
+ end
532
+
533
+ # create a sha2 hash of a file
534
+ def createHash(filePath)
535
+ hashfunc = Digest::SHA2.new
536
+ open(filePath, "rb") do |io|
537
+ while !io.eof
538
+ readBuffer = io.readpartial(1024)
539
+ hashfunc.update(readBuffer)
540
+ end
541
+ end
542
+ return hashfunc.hexdigest
543
+ end
544
+
545
+ # add an entry to the branch list
546
+ def addBranchEntry(entry)
547
+ branches = getBranches
548
+ branches << entry
549
+ saveBranches(branches)
550
+ end
551
+
552
+ # save off all branches
553
+ def saveBranches(branches)
554
+ saveDataToJson(File.join(@repoDir, "pointers"), branches)
555
+ end
556
+
557
+ # get the repository directory
558
+ def getRepositoryDirectory
559
+ while true
560
+ atRoot = true
561
+ Dir.foreach(Dir.getwd) do |dir|
562
+ if dir == ".tvc"
563
+ @runDir = Dir.getwd
564
+ Dir.chdir(".tvc")
565
+ return Dir.getwd
566
+ end
567
+ if dir == ".."
568
+ atRoot = false
569
+ end
570
+ end
571
+ if atRoot
572
+ Dir.chdir(@runDir)
573
+ return nil
574
+ else
575
+ Dir.chdir("..")
576
+ end
577
+ end
578
+ end
579
+
580
+ # list all existing branches
581
+ def listBranches
582
+ branches = getBranches
583
+ branches.each do |branch|
584
+ puts branch["name"]
585
+ end
586
+ end
587
+
588
+ # main function
589
+ def initialize(args)
590
+ # get directories for use later
591
+ @runDir= Dir.getwd
592
+ @repoDir = getRepositoryDirectory
593
+
594
+ # if we're not initializing and we have no repository
595
+ # issue an error and exit
596
+ if args[0] != "init" && @repoDir.nil?
597
+ repositoryIssueError
598
+ Process.exit
599
+ end
600
+ # switch based on command
601
+ case args[0]
602
+ when "init"
603
+ if @repoDir.nil?
604
+ init
605
+ else
606
+ puts "Repository already initialized"
607
+ end
608
+ when "commit"
609
+ commit args[1]
610
+ when "replace"
611
+ replace args[1]
612
+ when "rollback"
613
+ replace args[1]
614
+ commit "Rolled back to #{args[1]}"
615
+ when "reset"
616
+ replace(getCurrentEntry["hash"])
617
+ when "history"
618
+ history
619
+ when "branch"
620
+ if not args[1].nil?
621
+ branch args[1]
622
+ else
623
+ listBranches
624
+ end
625
+ when "checkout"
626
+ checkout args[1]
627
+ when "merge"
628
+ merge args[1]
629
+ when "status"
630
+ status
631
+ else
632
+ help
633
+ end
634
+ end
635
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'tvc'
16
+
17
+ class Test::Unit::TestCase
18
+ end
data/test/test_tvc.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestTvc < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
data/tvc.gemspec ADDED
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{tvc}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris O'Neal"]
12
+ s.date = %q{2011-03-25}
13
+ s.default_executable = %q{tvc}
14
+ s.description = %q{TVC is an awful git clone that should never be used, ever.}
15
+ s.email = %q{ctoneal@gmail.com}
16
+ s.executables = ["tvc"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/tvc",
29
+ "lib/tvc.rb",
30
+ "test/helper.rb",
31
+ "test/test_tvc.rb",
32
+ "tvc.gemspec"
33
+ ]
34
+ s.homepage = %q{http://github.com/ctoneal/tvc}
35
+ s.licenses = ["MIT"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.6.2}
38
+ s.summary = %q{TVC - Terrible Version Control}
39
+ s.test_files = [
40
+ "test/helper.rb",
41
+ "test/test_tvc.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
+ s.add_runtime_dependency(%q<json>, [">= 0"])
49
+ s.add_runtime_dependency(%q<diff-lcs>, [">= 0"])
50
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
51
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
52
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
53
+ s.add_development_dependency(%q<rcov>, [">= 0"])
54
+ else
55
+ s.add_dependency(%q<json>, [">= 0"])
56
+ s.add_dependency(%q<diff-lcs>, [">= 0"])
57
+ s.add_dependency(%q<shoulda>, [">= 0"])
58
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
60
+ s.add_dependency(%q<rcov>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<json>, [">= 0"])
64
+ s.add_dependency(%q<diff-lcs>, [">= 0"])
65
+ s.add_dependency(%q<shoulda>, [">= 0"])
66
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
68
+ s.add_dependency(%q<rcov>, [">= 0"])
69
+ end
70
+ end
71
+
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tvc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris O'Neal
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-03-25 00:00:00.000000000 -05:00
13
+ default_executable: tvc
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ requirement: &9782652 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *9782652
26
+ - !ruby/object:Gem::Dependency
27
+ name: diff-lcs
28
+ requirement: &9781788 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *9781788
37
+ - !ruby/object:Gem::Dependency
38
+ name: shoulda
39
+ requirement: &9764052 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *9764052
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ requirement: &9763044 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.0.0
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *9763044
59
+ - !ruby/object:Gem::Dependency
60
+ name: jeweler
61
+ requirement: &9761964 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 1.5.2
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *9761964
70
+ - !ruby/object:Gem::Dependency
71
+ name: rcov
72
+ requirement: &9760764 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *9760764
81
+ description: TVC is an awful git clone that should never be used, ever.
82
+ email: ctoneal@gmail.com
83
+ executables:
84
+ - tvc
85
+ extensions: []
86
+ extra_rdoc_files:
87
+ - LICENSE.txt
88
+ - README.rdoc
89
+ files:
90
+ - Gemfile
91
+ - Gemfile.lock
92
+ - LICENSE.txt
93
+ - README.rdoc
94
+ - Rakefile
95
+ - VERSION
96
+ - bin/tvc
97
+ - lib/tvc.rb
98
+ - test/helper.rb
99
+ - test/test_tvc.rb
100
+ - tvc.gemspec
101
+ has_rdoc: true
102
+ homepage: http://github.com/ctoneal/tvc
103
+ licenses:
104
+ - MIT
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ segments:
116
+ - 0
117
+ hash: -707806879
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.6.2
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: TVC - Terrible Version Control
130
+ test_files:
131
+ - test/helper.rb
132
+ - test/test_tvc.rb