jit 0.0.0 → 1.0.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.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +674 -0
  3. data/bin/jit +21 -0
  4. data/lib/color.rb +32 -0
  5. data/lib/command.rb +62 -0
  6. data/lib/command/add.rb +65 -0
  7. data/lib/command/base.rb +92 -0
  8. data/lib/command/branch.rb +199 -0
  9. data/lib/command/checkout.rb +104 -0
  10. data/lib/command/cherry_pick.rb +51 -0
  11. data/lib/command/commit.rb +86 -0
  12. data/lib/command/config.rb +126 -0
  13. data/lib/command/diff.rb +114 -0
  14. data/lib/command/fetch.rb +116 -0
  15. data/lib/command/init.rb +41 -0
  16. data/lib/command/log.rb +188 -0
  17. data/lib/command/merge.rb +148 -0
  18. data/lib/command/push.rb +172 -0
  19. data/lib/command/receive_pack.rb +92 -0
  20. data/lib/command/remote.rb +55 -0
  21. data/lib/command/reset.rb +64 -0
  22. data/lib/command/rev_list.rb +33 -0
  23. data/lib/command/revert.rb +69 -0
  24. data/lib/command/rm.rb +105 -0
  25. data/lib/command/shared/fast_forward.rb +19 -0
  26. data/lib/command/shared/print_diff.rb +116 -0
  27. data/lib/command/shared/receive_objects.rb +37 -0
  28. data/lib/command/shared/remote_agent.rb +44 -0
  29. data/lib/command/shared/remote_client.rb +82 -0
  30. data/lib/command/shared/send_objects.rb +24 -0
  31. data/lib/command/shared/sequencing.rb +146 -0
  32. data/lib/command/shared/write_commit.rb +167 -0
  33. data/lib/command/status.rb +210 -0
  34. data/lib/command/upload_pack.rb +54 -0
  35. data/lib/config.rb +240 -0
  36. data/lib/config/stack.rb +42 -0
  37. data/lib/database.rb +112 -0
  38. data/lib/database/author.rb +27 -0
  39. data/lib/database/backends.rb +57 -0
  40. data/lib/database/blob.rb +24 -0
  41. data/lib/database/commit.rb +70 -0
  42. data/lib/database/entry.rb +7 -0
  43. data/lib/database/loose.rb +70 -0
  44. data/lib/database/packed.rb +75 -0
  45. data/lib/database/tree.rb +77 -0
  46. data/lib/database/tree_diff.rb +88 -0
  47. data/lib/diff.rb +46 -0
  48. data/lib/diff/combined.rb +72 -0
  49. data/lib/diff/hunk.rb +64 -0
  50. data/lib/diff/myers.rb +90 -0
  51. data/lib/editor.rb +59 -0
  52. data/lib/index.rb +212 -0
  53. data/lib/index/checksum.rb +44 -0
  54. data/lib/index/entry.rb +91 -0
  55. data/lib/lockfile.rb +55 -0
  56. data/lib/merge/bases.rb +38 -0
  57. data/lib/merge/common_ancestors.rb +77 -0
  58. data/lib/merge/diff3.rb +156 -0
  59. data/lib/merge/inputs.rb +42 -0
  60. data/lib/merge/resolve.rb +178 -0
  61. data/lib/pack.rb +45 -0
  62. data/lib/pack/compressor.rb +83 -0
  63. data/lib/pack/delta.rb +58 -0
  64. data/lib/pack/entry.rb +54 -0
  65. data/lib/pack/expander.rb +54 -0
  66. data/lib/pack/index.rb +100 -0
  67. data/lib/pack/indexer.rb +200 -0
  68. data/lib/pack/numbers.rb +79 -0
  69. data/lib/pack/reader.rb +98 -0
  70. data/lib/pack/stream.rb +80 -0
  71. data/lib/pack/unpacker.rb +62 -0
  72. data/lib/pack/window.rb +47 -0
  73. data/lib/pack/writer.rb +92 -0
  74. data/lib/pack/xdelta.rb +118 -0
  75. data/lib/pager.rb +24 -0
  76. data/lib/progress.rb +78 -0
  77. data/lib/refs.rb +260 -0
  78. data/lib/remotes.rb +82 -0
  79. data/lib/remotes/protocol.rb +82 -0
  80. data/lib/remotes/refspec.rb +70 -0
  81. data/lib/remotes/remote.rb +57 -0
  82. data/lib/repository.rb +64 -0
  83. data/lib/repository/divergence.rb +21 -0
  84. data/lib/repository/hard_reset.rb +35 -0
  85. data/lib/repository/inspector.rb +49 -0
  86. data/lib/repository/migration.rb +168 -0
  87. data/lib/repository/pending_commit.rb +60 -0
  88. data/lib/repository/sequencer.rb +118 -0
  89. data/lib/repository/status.rb +98 -0
  90. data/lib/rev_list.rb +244 -0
  91. data/lib/revision.rb +155 -0
  92. data/lib/sorted_hash.rb +17 -0
  93. data/lib/temp_file.rb +34 -0
  94. data/lib/workspace.rb +107 -0
  95. metadata +103 -9
@@ -0,0 +1,212 @@
1
+ require "set"
2
+
3
+ require_relative "./index/checksum"
4
+ require_relative "./index/entry"
5
+ require_relative "./lockfile"
6
+
7
+ class Index
8
+ Invalid = Class.new(StandardError)
9
+
10
+ HEADER_SIZE = 12
11
+ HEADER_FORMAT = "a4N2"
12
+ SIGNATURE = "DIRC"
13
+ VERSION = 2
14
+
15
+ def initialize(pathname)
16
+ @pathname = pathname
17
+ @lockfile = Lockfile.new(pathname)
18
+ clear
19
+ end
20
+
21
+ def clear!
22
+ clear
23
+ @changed = true
24
+ end
25
+
26
+ def load_for_update
27
+ @lockfile.hold_for_update
28
+ load
29
+ end
30
+
31
+ def load
32
+ clear
33
+ file = open_index_file
34
+
35
+ if file
36
+ reader = Checksum.new(file)
37
+ count = read_header(reader)
38
+ read_entries(reader, count)
39
+ reader.verify_checksum
40
+ end
41
+ ensure
42
+ file&.close
43
+ end
44
+
45
+ def write_updates
46
+ return @lockfile.rollback unless @changed
47
+
48
+ writer = Checksum.new(@lockfile)
49
+
50
+ header = [SIGNATURE, VERSION, @entries.size].pack(HEADER_FORMAT)
51
+ writer.write(header)
52
+ each_entry { |entry| writer.write(entry.to_s) }
53
+
54
+ writer.write_checksum
55
+ @lockfile.commit
56
+
57
+ @changed = false
58
+ end
59
+
60
+ def release_lock
61
+ @lockfile.rollback
62
+ end
63
+
64
+ def add(pathname, oid, stat)
65
+ (1..3).each { |stage| remove_entry_with_stage(pathname, stage) }
66
+
67
+ entry = Entry.create(pathname, oid, stat)
68
+ discard_conflicts(entry)
69
+ store_entry(entry)
70
+ @changed = true
71
+ end
72
+
73
+ def add_from_db(pathname, item)
74
+ store_entry(Entry.create_from_db(pathname, item, 0))
75
+ @changed = true
76
+ end
77
+
78
+ def add_conflict_set(pathname, items)
79
+ remove_entry_with_stage(pathname, 0)
80
+
81
+ items.each_with_index do |item, n|
82
+ next unless item
83
+ entry = Entry.create_from_db(pathname, item, n + 1)
84
+ store_entry(entry)
85
+ end
86
+ @changed = true
87
+ end
88
+
89
+ def update_entry_stat(entry, stat)
90
+ entry.update_stat(stat)
91
+ @changed = true
92
+ end
93
+
94
+ def remove(pathname)
95
+ @parents[pathname.to_s].each { |child| remove_entry(child) }
96
+ remove_entry(pathname)
97
+ @changed = true
98
+ end
99
+
100
+ def each_entry
101
+ if block_given?
102
+ @keys.each { |key| yield @entries[key] }
103
+ else
104
+ enum_for(:each_entry)
105
+ end
106
+ end
107
+
108
+ def conflict?
109
+ @entries.any? { |key, entry| entry.stage > 0 }
110
+ end
111
+
112
+ def conflict_paths
113
+ paths = Set.new
114
+ each_entry { |entry| paths.add(entry.path) unless entry.stage == 0 }
115
+ paths
116
+ end
117
+
118
+ def entry_for_path(path, stage = 0)
119
+ @entries[[path.to_s, stage]]
120
+ end
121
+
122
+ def child_paths(path)
123
+ @parents[path.to_s].to_a
124
+ end
125
+
126
+ def tracked_file?(path)
127
+ (0..3).any? { |stage| @entries.has_key?([path.to_s, stage]) }
128
+ end
129
+
130
+ def tracked_directory?(path)
131
+ @parents.has_key?(path.to_s)
132
+ end
133
+
134
+ def tracked?(path)
135
+ tracked_file?(path) or tracked_directory?(path)
136
+ end
137
+
138
+ private
139
+
140
+ def clear
141
+ @entries = {}
142
+ @keys = SortedSet.new
143
+ @parents = Hash.new { |hash, key| hash[key] = Set.new }
144
+ @changed = false
145
+ end
146
+
147
+ def discard_conflicts(entry)
148
+ entry.parent_directories.each { |parent| remove_entry(parent) }
149
+
150
+ set = @parents.fetch(entry.path, [])
151
+ set.each { |child| remove_entry(child) }
152
+ end
153
+
154
+ def remove_entry(pathname)
155
+ (0..3).each { |stage| remove_entry_with_stage(pathname, stage) }
156
+ end
157
+
158
+ def remove_entry_with_stage(pathname, stage)
159
+ entry = @entries[[pathname.to_s, stage]]
160
+ return unless entry
161
+
162
+ @keys.delete(entry.key)
163
+ @entries.delete(entry.key)
164
+
165
+ entry.parent_directories.each do |dirname|
166
+ dir = dirname.to_s
167
+ @parents[dir].delete(entry.path)
168
+ @parents.delete(dir) if @parents[dir].empty?
169
+ end
170
+ end
171
+
172
+ def store_entry(entry)
173
+ @keys.add(entry.key)
174
+ @entries[entry.key] = entry
175
+
176
+ entry.parent_directories.each do |dirname|
177
+ @parents[dirname.to_s].add(entry.path)
178
+ end
179
+ end
180
+
181
+ def open_index_file
182
+ File.open(@pathname, File::RDONLY)
183
+ rescue Errno::ENOENT
184
+ nil
185
+ end
186
+
187
+ def read_header(reader)
188
+ data = reader.read(HEADER_SIZE)
189
+ signature, version, count = data.unpack(HEADER_FORMAT)
190
+
191
+ unless signature == SIGNATURE
192
+ raise Invalid, "Signature: expected '#{ SIGNATURE }' but found '#{ signature }'"
193
+ end
194
+ unless version == VERSION
195
+ raise Invalid, "Version: expected '#{ VERSION }' but found '#{ version }'"
196
+ end
197
+
198
+ count
199
+ end
200
+
201
+ def read_entries(reader, count)
202
+ count.times do
203
+ entry = reader.read(ENTRY_MIN_SIZE)
204
+
205
+ until entry.byteslice(-1) == "\0"
206
+ entry.concat(reader.read(ENTRY_BLOCK))
207
+ end
208
+
209
+ store_entry(Entry.parse(entry))
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,44 @@
1
+ require "digest/sha1"
2
+
3
+ class Index
4
+ class Checksum
5
+
6
+ EndOfFile = Class.new(StandardError)
7
+
8
+ CHECKSUM_SIZE = 20
9
+
10
+ def initialize(file)
11
+ @file = file
12
+ @digest = Digest::SHA1.new
13
+ end
14
+
15
+ def write(data)
16
+ @file.write(data)
17
+ @digest.update(data)
18
+ end
19
+
20
+ def write_checksum
21
+ @file.write(@digest.digest)
22
+ end
23
+
24
+ def read(size)
25
+ data = @file.read(size)
26
+
27
+ unless data.bytesize == size
28
+ raise EndOfFile, "Unexpected end-of-file while reading index"
29
+ end
30
+
31
+ @digest.update(data)
32
+ data
33
+ end
34
+
35
+ def verify_checksum
36
+ sum = @file.read(CHECKSUM_SIZE)
37
+
38
+ unless sum == @digest.digest
39
+ raise Invalid, "Checksum does not match value stored on disk"
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,91 @@
1
+ require "pathname"
2
+
3
+ class Index
4
+ ENTRY_FORMAT = "N10H40nZ*"
5
+ ENTRY_BLOCK = 8
6
+ ENTRY_MIN_SIZE = 64
7
+
8
+ REGULAR_MODE = 0100644
9
+ EXECUTABLE_MODE = 0100755
10
+ MAX_PATH_SIZE = 0xfff
11
+
12
+ entry_fields = [
13
+ :ctime, :ctime_nsec,
14
+ :mtime, :mtime_nsec,
15
+ :dev, :ino, :mode, :uid, :gid, :size,
16
+ :oid, :flags, :path
17
+ ]
18
+
19
+ Entry = Struct.new(*entry_fields) do
20
+ def self.create(pathname, oid, stat)
21
+ path = pathname.to_s
22
+ mode = mode_for_stat(stat)
23
+ flags = [path.bytesize, MAX_PATH_SIZE].min
24
+
25
+ Entry.new(
26
+ stat.ctime.to_i, stat.ctime.nsec,
27
+ stat.mtime.to_i, stat.mtime.nsec,
28
+ stat.dev, stat.ino, mode, stat.uid, stat.gid, stat.size,
29
+ oid, flags, path)
30
+ end
31
+
32
+ def self.create_from_db(pathname, item, n)
33
+ path = pathname.to_s
34
+ flags = (n << 12) | [path.bytesize, MAX_PATH_SIZE].min
35
+
36
+ Entry.new(0, 0, 0, 0, 0, 0, item.mode, 0, 0, 0, item.oid, flags, path)
37
+ end
38
+
39
+ def self.mode_for_stat(stat)
40
+ stat.executable? ? EXECUTABLE_MODE : REGULAR_MODE
41
+ end
42
+
43
+ def self.parse(data)
44
+ Entry.new(*data.unpack(ENTRY_FORMAT))
45
+ end
46
+
47
+ def key
48
+ [path, stage]
49
+ end
50
+
51
+ def stage
52
+ (flags >> 12) & 0x3
53
+ end
54
+
55
+ def parent_directories
56
+ Pathname.new(path).descend.to_a[0..-2]
57
+ end
58
+
59
+ def basename
60
+ Pathname.new(path).basename
61
+ end
62
+
63
+ def update_stat(stat)
64
+ self.ctime = stat.ctime.to_i
65
+ self.ctime_nsec = stat.ctime.nsec
66
+ self.mtime = stat.mtime.to_i
67
+ self.mtime_nsec = stat.mtime.nsec
68
+ self.dev = stat.dev
69
+ self.ino = stat.ino
70
+ self.mode = Entry.mode_for_stat(stat)
71
+ self.uid = stat.uid
72
+ self.gid = stat.gid
73
+ self.size = stat.size
74
+ end
75
+
76
+ def stat_match?(stat)
77
+ mode == Entry.mode_for_stat(stat) and (size == 0 or size == stat.size)
78
+ end
79
+
80
+ def times_match?(stat)
81
+ ctime == stat.ctime.to_i and ctime_nsec == stat.ctime.nsec and
82
+ mtime == stat.mtime.to_i and mtime_nsec == stat.mtime.nsec
83
+ end
84
+
85
+ def to_s
86
+ string = to_a.pack(ENTRY_FORMAT)
87
+ string.concat("\0") until string.bytesize % ENTRY_BLOCK == 0
88
+ string
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,55 @@
1
+ class Lockfile
2
+ LockDenied = Class.new(StandardError)
3
+ MissingParent = Class.new(StandardError)
4
+ NoPermission = Class.new(StandardError)
5
+ StaleLock = Class.new(StandardError)
6
+
7
+ def initialize(path)
8
+ @file_path = path
9
+ @lock_path = path.sub_ext(".lock")
10
+
11
+ @lock = nil
12
+ end
13
+
14
+ def hold_for_update
15
+ unless @lock
16
+ flags = File::RDWR | File::CREAT | File::EXCL
17
+ @lock = File.open(@lock_path, flags)
18
+ end
19
+ rescue Errno::EEXIST
20
+ raise LockDenied, "Unable to create '#{ @lock_path }': File exists."
21
+ rescue Errno::ENOENT => error
22
+ raise MissingParent, error.message
23
+ rescue Errno::EACCES => error
24
+ raise NoPermission, error.message
25
+ end
26
+
27
+ def write(string)
28
+ raise_on_stale_lock
29
+ @lock.write(string)
30
+ end
31
+
32
+ def commit
33
+ raise_on_stale_lock
34
+
35
+ @lock.close
36
+ File.rename(@lock_path, @file_path)
37
+ @lock = nil
38
+ end
39
+
40
+ def rollback
41
+ raise_on_stale_lock
42
+
43
+ @lock.close
44
+ File.unlink(@lock_path)
45
+ @lock = nil
46
+ end
47
+
48
+ private
49
+
50
+ def raise_on_stale_lock
51
+ unless @lock
52
+ raise StaleLock, "Not holding lock on file: #{ @lock_path }"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,38 @@
1
+ require "set"
2
+ require_relative "./common_ancestors"
3
+
4
+ module Merge
5
+ class Bases
6
+
7
+ def initialize(database, one, two)
8
+ @database = database
9
+ @common = CommonAncestors.new(@database, one, [two])
10
+ end
11
+
12
+ def find
13
+ @commits = @common.find
14
+ return @commits if @commits.size <= 1
15
+
16
+ @redundant = Set.new
17
+ @commits.each { |commit| filter_commit(commit) }
18
+ @commits - @redundant.to_a
19
+ end
20
+
21
+ private
22
+
23
+ def filter_commit(commit)
24
+ return if @redundant.include?(commit)
25
+
26
+ others = @commits - [commit, *@redundant]
27
+ common = CommonAncestors.new(@database, commit, others)
28
+
29
+ common.find
30
+
31
+ @redundant.add(commit) if common.marked?(commit, :parent2)
32
+
33
+ others.select! { |oid| common.marked?(oid, :parent1) }
34
+ @redundant.merge(others)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,77 @@
1
+ require "set"
2
+
3
+ module Merge
4
+ class CommonAncestors
5
+
6
+ BOTH_PARENTS = Set.new([:parent1, :parent2])
7
+
8
+ def initialize(database, one, twos)
9
+ @database = database
10
+ @flags = Hash.new { |hash, oid| hash[oid] = Set.new }
11
+ @queue = []
12
+ @results = []
13
+
14
+ insert_by_date(@queue, @database.load(one))
15
+ @flags[one].add(:parent1)
16
+
17
+ twos.each do |two|
18
+ insert_by_date(@queue, @database.load(two))
19
+ @flags[two].add(:parent2)
20
+ end
21
+ end
22
+
23
+ def find
24
+ process_queue until all_stale?
25
+ @results.map(&:oid).reject { |oid| marked?(oid, :stale) }
26
+ end
27
+
28
+ def marked?(oid, flag)
29
+ @flags[oid].include?(flag)
30
+ end
31
+
32
+ def counts
33
+ ones, twos = 0, 0
34
+
35
+ @flags.each do |oid, flags|
36
+ next unless flags.size == 1
37
+ ones += 1 if flags.include?(:parent1)
38
+ twos += 1 if flags.include?(:parent2)
39
+ end
40
+
41
+ [ones, twos]
42
+ end
43
+
44
+ private
45
+
46
+ def all_stale?
47
+ @queue.all? { |commit| marked?(commit.oid, :stale) }
48
+ end
49
+
50
+ def process_queue
51
+ commit = @queue.shift
52
+ flags = @flags[commit.oid]
53
+
54
+ if flags == BOTH_PARENTS
55
+ flags.add(:result)
56
+ insert_by_date(@results, commit)
57
+ add_parents(commit, flags + [:stale])
58
+ else
59
+ add_parents(commit, flags)
60
+ end
61
+ end
62
+
63
+ def add_parents(commit, flags)
64
+ commit.parents.each do |parent|
65
+ next if @flags[parent].superset?(flags)
66
+
67
+ @flags[parent].merge(flags)
68
+ insert_by_date(@queue, @database.load(parent))
69
+ end
70
+ end
71
+
72
+ def insert_by_date(list, commit)
73
+ index = list.find_index { |c| c.date < commit.date }
74
+ list.insert(index || list.size, commit)
75
+ end
76
+ end
77
+ end