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,156 @@
1
+ require_relative "../diff"
2
+
3
+ module Merge
4
+ class Diff3
5
+
6
+ Clean = Struct.new(:lines) do
7
+ def to_s(*)
8
+ lines.join("")
9
+ end
10
+ end
11
+
12
+ Conflict = Struct.new(:o_lines, :a_lines, :b_lines) do
13
+ def to_s(a_name = nil, b_name = nil)
14
+ text = ""
15
+ separator(text, "<", a_name)
16
+ a_lines.each { |line| text.concat(line) }
17
+ separator(text, "=")
18
+ b_lines.each { |line| text.concat(line) }
19
+ separator(text, ">", b_name)
20
+ text
21
+ end
22
+
23
+ def separator(text, char, name = nil)
24
+ text.concat(char * 7)
25
+ text.concat(" #{ name }") if name
26
+ text.concat("\n")
27
+ end
28
+ end
29
+
30
+ Result = Struct.new(:chunks) do
31
+ def clean?
32
+ chunks.none? { |chunk| chunk.is_a?(Conflict) }
33
+ end
34
+
35
+ def to_s(a_name = nil, b_name = nil)
36
+ chunks.map { |chunk| chunk.to_s(a_name, b_name) }.join("")
37
+ end
38
+ end
39
+
40
+ def self.merge(o, a, b)
41
+ o = o.lines if o.is_a?(String)
42
+ a = a.lines if a.is_a?(String)
43
+ b = b.lines if b.is_a?(String)
44
+
45
+ new(o, a, b).merge
46
+ end
47
+
48
+ def initialize(o, a, b)
49
+ @o, @a, @b = o, a, b
50
+ end
51
+
52
+ def merge
53
+ setup
54
+ generate_chunks
55
+ Result.new(@chunks)
56
+ end
57
+
58
+ def setup
59
+ @chunks = []
60
+ @line_o = @line_a = @line_b = 0
61
+
62
+ @match_a = match_set(@a)
63
+ @match_b = match_set(@b)
64
+ end
65
+
66
+ def match_set(file)
67
+ matches = {}
68
+
69
+ Diff.diff(@o, file).each do |edit|
70
+ next unless edit.type == :eql
71
+ matches[edit.a_line.number] = edit.b_line.number
72
+ end
73
+
74
+ matches
75
+ end
76
+
77
+ def generate_chunks
78
+ loop do
79
+ i = find_next_mismatch
80
+
81
+ if i == 1
82
+ o, a, b = find_next_match
83
+
84
+ if a and b
85
+ emit_chunk(o, a, b)
86
+ else
87
+ emit_final_chunk
88
+ return
89
+ end
90
+
91
+ elsif i
92
+ emit_chunk(@line_o + i, @line_a + i, @line_b + i)
93
+
94
+ else
95
+ emit_final_chunk
96
+ return
97
+ end
98
+ end
99
+ end
100
+
101
+ def find_next_mismatch
102
+ i = 1
103
+ while in_bounds?(i) and
104
+ match?(@match_a, @line_a, i) and
105
+ match?(@match_b, @line_b, i)
106
+ i += 1
107
+ end
108
+ in_bounds?(i) ? i : nil
109
+ end
110
+
111
+ def in_bounds?(i)
112
+ @line_o + i <= @o.size or
113
+ @line_a + i <= @a.size or
114
+ @line_b + i <= @b.size
115
+ end
116
+
117
+ def match?(matches, offset, i)
118
+ matches[@line_o + i] == offset + i
119
+ end
120
+
121
+ def find_next_match
122
+ o = @line_o + 1
123
+ until o > @o.size or (@match_a.has_key?(o) and @match_b.has_key?(o))
124
+ o += 1
125
+ end
126
+ [o, @match_a[o], @match_b[o]]
127
+ end
128
+
129
+ def emit_chunk(o, a, b)
130
+ write_chunk(
131
+ @o[@line_o ... o - 1],
132
+ @a[@line_a ... a - 1],
133
+ @b[@line_b ... b - 1])
134
+
135
+ @line_o, @line_a, @line_b = o - 1, a - 1, b - 1
136
+ end
137
+
138
+ def emit_final_chunk
139
+ write_chunk(
140
+ @o[@line_o .. -1],
141
+ @a[@line_a .. -1],
142
+ @b[@line_b .. -1])
143
+ end
144
+
145
+ def write_chunk(o, a, b)
146
+ if a == o or a == b
147
+ @chunks.push(Clean.new(b))
148
+ elsif b == o
149
+ @chunks.push(Clean.new(a))
150
+ else
151
+ @chunks.push(Conflict.new(o, a, b))
152
+ end
153
+ end
154
+
155
+ end
156
+ end
@@ -0,0 +1,42 @@
1
+ require_relative "./bases"
2
+ require_relative "../revision"
3
+
4
+ module Merge
5
+ class Inputs
6
+
7
+ ATTRS = [ :left_name, :right_name,
8
+ :left_oid, :right_oid,
9
+ :base_oids ]
10
+
11
+ attr_reader(*ATTRS)
12
+
13
+ def initialize(repository, left_name, right_name)
14
+ @repo = repository
15
+ @left_name = left_name
16
+ @right_name = right_name
17
+
18
+ @left_oid = resolve_rev(@left_name)
19
+ @right_oid = resolve_rev(@right_name)
20
+
21
+ common = Bases.new(@repo.database, @left_oid, @right_oid)
22
+ @base_oids = common.find
23
+ end
24
+
25
+ def already_merged?
26
+ @base_oids == [@right_oid]
27
+ end
28
+
29
+ def fast_forward?
30
+ @base_oids == [@left_oid]
31
+ end
32
+
33
+ private
34
+
35
+ def resolve_rev(rev)
36
+ Revision.new(@repo, rev).resolve(Revision::COMMIT)
37
+ end
38
+
39
+ end
40
+
41
+ CherryPick = Struct.new(*Inputs::ATTRS)
42
+ end
@@ -0,0 +1,178 @@
1
+ require_relative "./diff3"
2
+
3
+ module Merge
4
+ class Resolve
5
+
6
+ def initialize(repository, inputs)
7
+ @repo = repository
8
+ @inputs = inputs
9
+
10
+ @on_progress = nil
11
+ end
12
+
13
+ def on_progress(&block)
14
+ @on_progress = block
15
+ end
16
+
17
+ def execute
18
+ prepare_tree_diffs
19
+
20
+ migration = @repo.migration(@clean_diff)
21
+ migration.apply_changes
22
+
23
+ add_conflicts_to_index
24
+ write_untracked_files
25
+ end
26
+
27
+ private
28
+
29
+ def prepare_tree_diffs
30
+ base_oid = @inputs.base_oids.first
31
+ @left_diff = @repo.database.tree_diff(base_oid, @inputs.left_oid)
32
+ @right_diff = @repo.database.tree_diff(base_oid, @inputs.right_oid)
33
+ @clean_diff = {}
34
+ @conflicts = {}
35
+ @untracked = {}
36
+
37
+ @right_diff.each do |path, (old_item, new_item)|
38
+ file_dir_conflict(path, @left_diff, @inputs.left_name) if new_item
39
+ same_path_conflict(path, old_item, new_item)
40
+ end
41
+
42
+ @left_diff.each do |path, (_, new_item)|
43
+ file_dir_conflict(path, @right_diff, @inputs.right_name) if new_item
44
+ end
45
+ end
46
+
47
+ def same_path_conflict(path, base, right)
48
+ return if @conflicts[path]
49
+
50
+ unless @left_diff.has_key?(path)
51
+ @clean_diff[path] = [base, right]
52
+ return
53
+ end
54
+
55
+ left = @left_diff[path][1]
56
+ return if left == right
57
+
58
+ log "Auto-merging #{ path }" if left and right
59
+
60
+ oid_ok, oid = merge_blobs(base&.oid, left&.oid, right&.oid)
61
+ mode_ok, mode = merge_modes(base&.mode, left&.mode, right&.mode)
62
+
63
+ @clean_diff[path] = [left, Database::Entry.new(oid, mode)]
64
+ return if oid_ok and mode_ok
65
+
66
+ @conflicts[path] = [base, left, right]
67
+ log_conflict(path)
68
+ end
69
+
70
+ def merge_blobs(base_oid, left_oid, right_oid)
71
+ result = merge3(base_oid, left_oid, right_oid)
72
+ return result if result
73
+
74
+ oids = [base_oid, left_oid, right_oid]
75
+ blobs = oids.map { |oid| oid ? @repo.database.load(oid).data : "" }
76
+ merge = Diff3.merge(*blobs)
77
+
78
+ data = merge.to_s(@inputs.left_name, @inputs.right_name)
79
+ blob = Database::Blob.new(data)
80
+ @repo.database.store(blob)
81
+
82
+ [merge.clean?, blob.oid]
83
+ end
84
+
85
+ def merge_modes(base_mode, left_mode, right_mode)
86
+ merge3(base_mode, left_mode, right_mode) || [false, left_mode]
87
+ end
88
+
89
+ def merge3(base, left, right)
90
+ return [false, right] unless left
91
+ return [false, left] unless right
92
+
93
+ if left == base or left == right
94
+ [true, right]
95
+ elsif right == base
96
+ [true, left]
97
+ end
98
+ end
99
+
100
+ def file_dir_conflict(path, diff, name)
101
+ path.dirname.ascend do |parent|
102
+ old_item, new_item = diff[parent]
103
+ next unless new_item
104
+
105
+ @conflicts[parent] = case name
106
+ when @inputs.left_name then [old_item, new_item, nil]
107
+ when @inputs.right_name then [old_item, nil, new_item]
108
+ end
109
+
110
+ @clean_diff.delete(parent)
111
+ rename = "#{ parent }~#{ name }"
112
+ @untracked[rename] = new_item
113
+
114
+ log "Adding #{ path }" unless diff[path]
115
+ log_conflict(parent, rename)
116
+ end
117
+ end
118
+
119
+ def add_conflicts_to_index
120
+ @conflicts.each do |path, items|
121
+ @repo.index.add_conflict_set(path, items)
122
+ end
123
+ end
124
+
125
+ def write_untracked_files
126
+ @untracked.each do |path, item|
127
+ blob = @repo.database.load(item.oid)
128
+ @repo.workspace.write_file(path, blob.data)
129
+ end
130
+ end
131
+
132
+ def log(message)
133
+ @on_progress&.call(message)
134
+ end
135
+
136
+ def log_conflict(path, rename = nil)
137
+ base, left, right = @conflicts[path]
138
+
139
+ if left and right
140
+ log_left_right_conflict(path)
141
+ elsif base and (left or right)
142
+ log_modify_delete_conflict(path, rename)
143
+ else
144
+ log_file_directory_conflict(path, rename)
145
+ end
146
+ end
147
+
148
+ def log_left_right_conflict(path)
149
+ type = @conflicts[path][0] ? "content" : "add/add"
150
+ log "CONFLICT (#{ type }): Merge conflict in #{ path }"
151
+ end
152
+
153
+ def log_modify_delete_conflict(path, rename)
154
+ deleted, modified = log_branch_names(path)
155
+
156
+ rename = rename ? " at #{ rename }" : ""
157
+
158
+ log "CONFLICT (modify/delete): #{ path } " +
159
+ "deleted in #{ deleted } and modified in #{ modified }. " +
160
+ "Version #{ modified } of #{ path } left in tree#{ rename }."
161
+ end
162
+
163
+ def log_file_directory_conflict(path, rename)
164
+ type = @conflicts[path][1] ? "file/directory" : "directory/file"
165
+ branch, _ = log_branch_names(path)
166
+
167
+ log "CONFLICT (#{ type }): There is a directory " +
168
+ "with name #{ path } in #{ branch }. " +
169
+ "Adding #{ path } as #{ rename }"
170
+ end
171
+
172
+ def log_branch_names(path)
173
+ a, b = @inputs.left_name, @inputs.right_name
174
+ @conflicts[path][1] ? [b, a] : [a, b]
175
+ end
176
+
177
+ end
178
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "./pack/reader"
2
+ require_relative "./pack/writer"
3
+ require_relative "./pack/stream"
4
+ require_relative "./pack/indexer"
5
+ require_relative "./pack/unpacker"
6
+
7
+ module Pack
8
+ HEADER_SIZE = 12
9
+ HEADER_FORMAT = "a4N2"
10
+ SIGNATURE = "PACK"
11
+ VERSION = 2
12
+
13
+ GIT_MAX_COPY = 0x10000
14
+ MAX_COPY_SIZE = 0xffffff
15
+ MAX_INSERT_SIZE = 0x7f
16
+
17
+ IDX_SIGNATURE = 0xff744f63
18
+ IDX_MAX_OFFSET = 0x80000000
19
+
20
+ COMMIT = 1
21
+ TREE = 2
22
+ BLOB = 3
23
+
24
+ OFS_DELTA = 6
25
+ REF_DELTA = 7
26
+
27
+ TYPE_CODES = {
28
+ "commit" => COMMIT,
29
+ "tree" => TREE,
30
+ "blob" => BLOB
31
+ }
32
+
33
+ InvalidPack = Class.new(StandardError)
34
+
35
+ Record = Struct.new(:type, :data) do
36
+ attr_accessor :oid
37
+
38
+ def to_s
39
+ data
40
+ end
41
+ end
42
+
43
+ OfsDelta = Struct.new(:base_ofs, :delta_data)
44
+ RefDelta = Struct.new(:base_oid, :delta_data)
45
+ end
@@ -0,0 +1,83 @@
1
+ require_relative "./delta"
2
+ require_relative "./window"
3
+
4
+ module Pack
5
+ class Compressor
6
+
7
+ OBJECT_SIZE = 50..0x20000000
8
+ MAX_DEPTH = 50
9
+ WINDOW_SIZE = 8
10
+
11
+ def initialize(database, progress)
12
+ @database = database
13
+ @window = Window.new(WINDOW_SIZE)
14
+ @progress = progress
15
+ @objects = []
16
+ end
17
+
18
+ def add(entry)
19
+ return unless OBJECT_SIZE.include?(entry.size)
20
+ @objects.push(entry)
21
+ end
22
+
23
+ def build_deltas
24
+ @progress&.start("Compressing objects", @objects.size)
25
+
26
+ @objects.sort! { |a, b| b.sort_key <=> a.sort_key }
27
+
28
+ @objects.each do |entry|
29
+ build_delta(entry)
30
+ @progress&.tick
31
+ end
32
+ @progress&.stop
33
+ end
34
+
35
+ private
36
+
37
+ def build_delta(entry)
38
+ object = @database.load_raw(entry.oid)
39
+ target = @window.add(entry, object.data)
40
+
41
+ @window.each { |source| try_delta(source, target) }
42
+ end
43
+
44
+ def try_delta(source, target)
45
+ return unless source.type == target.type
46
+ return unless source.depth < MAX_DEPTH
47
+
48
+ max_size = max_size_heuristic(source, target)
49
+ return unless compatible_sizes?(source, target, max_size)
50
+
51
+ delta = Delta.new(source, target)
52
+ size = target.entry.packed_size
53
+
54
+ return if delta.size > max_size
55
+ return if delta.size == size and delta.base.depth + 1 >= target.depth
56
+
57
+ target.entry.assign_delta(delta)
58
+ end
59
+
60
+ def max_size_heuristic(source, target)
61
+ if target.delta
62
+ max_size = target.delta.size
63
+ ref_depth = target.depth
64
+ else
65
+ max_size = target.size / 2 - 20
66
+ ref_depth = 1
67
+ end
68
+
69
+ max_size * (MAX_DEPTH - source.depth) / (MAX_DEPTH + 1 - ref_depth)
70
+ end
71
+
72
+ def compatible_sizes?(source, target, max_size)
73
+ size_diff = [target.size - source.size, 0].max
74
+
75
+ return false if max_size == 0
76
+ return false if size_diff >= max_size
77
+ return false if target.size < source.size / 32
78
+
79
+ true
80
+ end
81
+
82
+ end
83
+ end