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.
- checksums.yaml +5 -5
- data/LICENSE.txt +674 -0
- data/bin/jit +21 -0
- data/lib/color.rb +32 -0
- data/lib/command.rb +62 -0
- data/lib/command/add.rb +65 -0
- data/lib/command/base.rb +92 -0
- data/lib/command/branch.rb +199 -0
- data/lib/command/checkout.rb +104 -0
- data/lib/command/cherry_pick.rb +51 -0
- data/lib/command/commit.rb +86 -0
- data/lib/command/config.rb +126 -0
- data/lib/command/diff.rb +114 -0
- data/lib/command/fetch.rb +116 -0
- data/lib/command/init.rb +41 -0
- data/lib/command/log.rb +188 -0
- data/lib/command/merge.rb +148 -0
- data/lib/command/push.rb +172 -0
- data/lib/command/receive_pack.rb +92 -0
- data/lib/command/remote.rb +55 -0
- data/lib/command/reset.rb +64 -0
- data/lib/command/rev_list.rb +33 -0
- data/lib/command/revert.rb +69 -0
- data/lib/command/rm.rb +105 -0
- data/lib/command/shared/fast_forward.rb +19 -0
- data/lib/command/shared/print_diff.rb +116 -0
- data/lib/command/shared/receive_objects.rb +37 -0
- data/lib/command/shared/remote_agent.rb +44 -0
- data/lib/command/shared/remote_client.rb +82 -0
- data/lib/command/shared/send_objects.rb +24 -0
- data/lib/command/shared/sequencing.rb +146 -0
- data/lib/command/shared/write_commit.rb +167 -0
- data/lib/command/status.rb +210 -0
- data/lib/command/upload_pack.rb +54 -0
- data/lib/config.rb +240 -0
- data/lib/config/stack.rb +42 -0
- data/lib/database.rb +112 -0
- data/lib/database/author.rb +27 -0
- data/lib/database/backends.rb +57 -0
- data/lib/database/blob.rb +24 -0
- data/lib/database/commit.rb +70 -0
- data/lib/database/entry.rb +7 -0
- data/lib/database/loose.rb +70 -0
- data/lib/database/packed.rb +75 -0
- data/lib/database/tree.rb +77 -0
- data/lib/database/tree_diff.rb +88 -0
- data/lib/diff.rb +46 -0
- data/lib/diff/combined.rb +72 -0
- data/lib/diff/hunk.rb +64 -0
- data/lib/diff/myers.rb +90 -0
- data/lib/editor.rb +59 -0
- data/lib/index.rb +212 -0
- data/lib/index/checksum.rb +44 -0
- data/lib/index/entry.rb +91 -0
- data/lib/lockfile.rb +55 -0
- data/lib/merge/bases.rb +38 -0
- data/lib/merge/common_ancestors.rb +77 -0
- data/lib/merge/diff3.rb +156 -0
- data/lib/merge/inputs.rb +42 -0
- data/lib/merge/resolve.rb +178 -0
- data/lib/pack.rb +45 -0
- data/lib/pack/compressor.rb +83 -0
- data/lib/pack/delta.rb +58 -0
- data/lib/pack/entry.rb +54 -0
- data/lib/pack/expander.rb +54 -0
- data/lib/pack/index.rb +100 -0
- data/lib/pack/indexer.rb +200 -0
- data/lib/pack/numbers.rb +79 -0
- data/lib/pack/reader.rb +98 -0
- data/lib/pack/stream.rb +80 -0
- data/lib/pack/unpacker.rb +62 -0
- data/lib/pack/window.rb +47 -0
- data/lib/pack/writer.rb +92 -0
- data/lib/pack/xdelta.rb +118 -0
- data/lib/pager.rb +24 -0
- data/lib/progress.rb +78 -0
- data/lib/refs.rb +260 -0
- data/lib/remotes.rb +82 -0
- data/lib/remotes/protocol.rb +82 -0
- data/lib/remotes/refspec.rb +70 -0
- data/lib/remotes/remote.rb +57 -0
- data/lib/repository.rb +64 -0
- data/lib/repository/divergence.rb +21 -0
- data/lib/repository/hard_reset.rb +35 -0
- data/lib/repository/inspector.rb +49 -0
- data/lib/repository/migration.rb +168 -0
- data/lib/repository/pending_commit.rb +60 -0
- data/lib/repository/sequencer.rb +118 -0
- data/lib/repository/status.rb +98 -0
- data/lib/rev_list.rb +244 -0
- data/lib/revision.rb +155 -0
- data/lib/sorted_hash.rb +17 -0
- data/lib/temp_file.rb +34 -0
- data/lib/workspace.rb +107 -0
- metadata +103 -9
@@ -0,0 +1,168 @@
|
|
1
|
+
require "set"
|
2
|
+
require_relative "./inspector"
|
3
|
+
|
4
|
+
class Repository
|
5
|
+
class Migration
|
6
|
+
|
7
|
+
Conflict = Class.new(StandardError)
|
8
|
+
|
9
|
+
MESSAGES = {
|
10
|
+
:stale_file => [
|
11
|
+
"Your local changes to the following files would be overwritten by checkout:",
|
12
|
+
"Please commit your changes or stash them before you switch branches."
|
13
|
+
],
|
14
|
+
:stale_directory => [
|
15
|
+
"Updating the following directories would lose untracked files in them:",
|
16
|
+
"\n"
|
17
|
+
],
|
18
|
+
:untracked_overwritten => [
|
19
|
+
"The following untracked working tree files would be overwritten by checkout:",
|
20
|
+
"Please move or remove them before you switch branches."
|
21
|
+
],
|
22
|
+
:untracked_removed => [
|
23
|
+
"The following untracked working tree files would be removed by checkout:",
|
24
|
+
"Please move or remove them before you switch branches."
|
25
|
+
]
|
26
|
+
}
|
27
|
+
|
28
|
+
attr_reader :changes, :mkdirs, :rmdirs, :errors
|
29
|
+
|
30
|
+
def initialize(repository, tree_diff)
|
31
|
+
@repo = repository
|
32
|
+
@diff = tree_diff
|
33
|
+
|
34
|
+
@inspector = Inspector.new(repository)
|
35
|
+
|
36
|
+
@changes = { :create => [], :update => [], :delete => [] }
|
37
|
+
@mkdirs = Set.new
|
38
|
+
@rmdirs = Set.new
|
39
|
+
@errors = []
|
40
|
+
|
41
|
+
@conflicts = {
|
42
|
+
:stale_file => SortedSet.new,
|
43
|
+
:stale_directory => SortedSet.new,
|
44
|
+
:untracked_overwritten => SortedSet.new,
|
45
|
+
:untracked_removed => SortedSet.new
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def apply_changes
|
50
|
+
plan_changes
|
51
|
+
update_workspace
|
52
|
+
update_index
|
53
|
+
end
|
54
|
+
|
55
|
+
def blob_data(oid)
|
56
|
+
@repo.database.load(oid).data
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def plan_changes
|
62
|
+
@diff.each do |path, (old_item, new_item)|
|
63
|
+
check_for_conflict(path, old_item, new_item)
|
64
|
+
record_change(path, old_item, new_item)
|
65
|
+
end
|
66
|
+
|
67
|
+
collect_errors
|
68
|
+
end
|
69
|
+
|
70
|
+
def update_workspace
|
71
|
+
@repo.workspace.apply_migration(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_index
|
75
|
+
@changes[:delete].each do |path, _|
|
76
|
+
@repo.index.remove(path)
|
77
|
+
end
|
78
|
+
|
79
|
+
[:create, :update].each do |action|
|
80
|
+
@changes[action].each do |path, entry|
|
81
|
+
stat = @repo.workspace.stat_file(path)
|
82
|
+
@repo.index.add(path, entry.oid, stat)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def record_change(path, old_item, new_item)
|
88
|
+
if old_item == nil
|
89
|
+
@mkdirs.merge(path.dirname.descend)
|
90
|
+
action = :create
|
91
|
+
elsif new_item == nil
|
92
|
+
@rmdirs.merge(path.dirname.descend)
|
93
|
+
action = :delete
|
94
|
+
else
|
95
|
+
@mkdirs.merge(path.dirname.descend)
|
96
|
+
action = :update
|
97
|
+
end
|
98
|
+
@changes[action].push([path, new_item])
|
99
|
+
end
|
100
|
+
|
101
|
+
def check_for_conflict(path, old_item, new_item)
|
102
|
+
entry = @repo.index.entry_for_path(path)
|
103
|
+
|
104
|
+
if index_differs_from_trees(entry, old_item, new_item)
|
105
|
+
@conflicts[:stale_file].add(path.to_s)
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
stat = @repo.workspace.stat_file(path)
|
110
|
+
type = get_error_type(stat, entry, new_item)
|
111
|
+
|
112
|
+
if stat == nil
|
113
|
+
parent = untracked_parent(path)
|
114
|
+
@conflicts[type].add(entry ? path.to_s : parent.to_s) if parent
|
115
|
+
|
116
|
+
elsif stat.file?
|
117
|
+
changed = @inspector.compare_index_to_workspace(entry, stat)
|
118
|
+
@conflicts[type].add(path.to_s) if changed
|
119
|
+
|
120
|
+
elsif stat.directory?
|
121
|
+
trackable = @inspector.trackable_file?(path, stat)
|
122
|
+
@conflicts[type].add(path.to_s) if trackable
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_error_type(stat, entry, item)
|
127
|
+
if entry
|
128
|
+
:stale_file
|
129
|
+
elsif stat&.directory?
|
130
|
+
:stale_directory
|
131
|
+
elsif item
|
132
|
+
:untracked_overwritten
|
133
|
+
else
|
134
|
+
:untracked_removed
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def index_differs_from_trees(entry, old_item, new_item)
|
139
|
+
@inspector.compare_tree_to_index(old_item, entry) and
|
140
|
+
@inspector.compare_tree_to_index(new_item, entry)
|
141
|
+
end
|
142
|
+
|
143
|
+
def untracked_parent(path)
|
144
|
+
path.dirname.ascend.find do |parent|
|
145
|
+
next if parent.to_s == "."
|
146
|
+
|
147
|
+
parent_stat = @repo.workspace.stat_file(parent)
|
148
|
+
next unless parent_stat&.file?
|
149
|
+
|
150
|
+
@inspector.trackable_file?(parent, parent_stat)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def collect_errors
|
155
|
+
@conflicts.each do |type, paths|
|
156
|
+
next if paths.empty?
|
157
|
+
|
158
|
+
lines = paths.map { |name| "\t#{ name }" }
|
159
|
+
header, footer = MESSAGES.fetch(type)
|
160
|
+
|
161
|
+
@errors.push([header, *lines, footer].join("\n"))
|
162
|
+
end
|
163
|
+
|
164
|
+
raise Conflict unless @errors.empty?
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Repository
|
2
|
+
class PendingCommit
|
3
|
+
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
|
6
|
+
HEAD_FILES = {
|
7
|
+
:merge => "MERGE_HEAD",
|
8
|
+
:cherry_pick => "CHERRY_PICK_HEAD",
|
9
|
+
:revert => "REVERT_HEAD"
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_reader :message_path
|
13
|
+
|
14
|
+
def initialize(pathname)
|
15
|
+
@pathname = pathname
|
16
|
+
@message_path = pathname.join("MERGE_MSG")
|
17
|
+
end
|
18
|
+
|
19
|
+
def start(oid, type = :merge)
|
20
|
+
path = @pathname.join(HEAD_FILES.fetch(type))
|
21
|
+
flags = File::WRONLY | File::CREAT | File::EXCL
|
22
|
+
File.open(path, flags) { |f| f.puts(oid) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def in_progress?
|
26
|
+
not merge_type.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def merge_type
|
30
|
+
HEAD_FILES.each do |type, name|
|
31
|
+
path = @pathname.join(name)
|
32
|
+
return type if File.file?(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge_oid(type = :merge)
|
39
|
+
head_path = @pathname.join(HEAD_FILES.fetch(type))
|
40
|
+
File.read(head_path).strip
|
41
|
+
rescue Errno::ENOENT
|
42
|
+
name = head_path.basename
|
43
|
+
raise Error, "There is no merge in progress (#{ name } missing)."
|
44
|
+
end
|
45
|
+
|
46
|
+
def merge_message
|
47
|
+
File.read(@message_path)
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear(type = :merge)
|
51
|
+
head_path = @pathname.join(HEAD_FILES.fetch(type))
|
52
|
+
File.unlink(head_path)
|
53
|
+
File.unlink(@message_path)
|
54
|
+
rescue Errno::ENOENT
|
55
|
+
name = head_path.basename
|
56
|
+
raise Error, "There is no merge to abort (#{ name } missing)."
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
require_relative "../config"
|
4
|
+
require_relative "../lockfile"
|
5
|
+
|
6
|
+
class Repository
|
7
|
+
class Sequencer
|
8
|
+
|
9
|
+
UNSAFE_MESSAGE = "You seem to have moved HEAD. Not rewinding, check your HEAD!"
|
10
|
+
|
11
|
+
def initialize(repository)
|
12
|
+
@repo = repository
|
13
|
+
@pathname = repository.git_path.join("sequencer")
|
14
|
+
@abort_path = @pathname.join("abort-safety")
|
15
|
+
@head_path = @pathname.join("head")
|
16
|
+
@todo_path = @pathname.join("todo")
|
17
|
+
@config = Config.new(@pathname.join("opts"))
|
18
|
+
@todo_file = nil
|
19
|
+
@commands = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def start(options)
|
23
|
+
Dir.mkdir(@pathname)
|
24
|
+
|
25
|
+
head_oid = @repo.refs.read_head
|
26
|
+
write_file(@head_path, head_oid)
|
27
|
+
write_file(@abort_path, head_oid)
|
28
|
+
|
29
|
+
@config.open_for_update
|
30
|
+
options.each { |key, value| @config.set(["options", key], value) }
|
31
|
+
@config.save
|
32
|
+
|
33
|
+
open_todo_file
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_option(name)
|
37
|
+
@config.open
|
38
|
+
@config.get(["options", name])
|
39
|
+
end
|
40
|
+
|
41
|
+
def pick(commit)
|
42
|
+
@commands.push([:pick, commit])
|
43
|
+
end
|
44
|
+
|
45
|
+
def revert(commit)
|
46
|
+
@commands.push([:revert, commit])
|
47
|
+
end
|
48
|
+
|
49
|
+
def next_command
|
50
|
+
@commands.first
|
51
|
+
end
|
52
|
+
|
53
|
+
def drop_command
|
54
|
+
@commands.shift
|
55
|
+
write_file(@abort_path, @repo.refs.read_head)
|
56
|
+
end
|
57
|
+
|
58
|
+
def load
|
59
|
+
open_todo_file
|
60
|
+
return unless File.file?(@todo_path)
|
61
|
+
|
62
|
+
@commands = File.read(@todo_path).lines.map do |line|
|
63
|
+
action, oid, _ = /^(\S+) (\S+) (.*)$/.match(line).captures
|
64
|
+
|
65
|
+
oids = @repo.database.prefix_match(oid)
|
66
|
+
commit = @repo.database.load(oids.first)
|
67
|
+
[action.to_sym, commit]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def dump
|
72
|
+
return unless @todo_file
|
73
|
+
|
74
|
+
@commands.each do |action, commit|
|
75
|
+
short = @repo.database.short_oid(commit.oid)
|
76
|
+
@todo_file.write("#{ action } #{ short } #{ commit.title_line }")
|
77
|
+
end
|
78
|
+
|
79
|
+
@todo_file.commit
|
80
|
+
end
|
81
|
+
|
82
|
+
def abort
|
83
|
+
head_oid = File.read(@head_path).strip
|
84
|
+
expected = File.read(@abort_path).strip
|
85
|
+
actual = @repo.refs.read_head
|
86
|
+
|
87
|
+
quit
|
88
|
+
|
89
|
+
raise UNSAFE_MESSAGE unless actual == expected
|
90
|
+
|
91
|
+
@repo.hard_reset(head_oid)
|
92
|
+
orig_head = @repo.refs.update_head(head_oid)
|
93
|
+
@repo.refs.update_ref(Refs::ORIG_HEAD, orig_head)
|
94
|
+
end
|
95
|
+
|
96
|
+
def quit
|
97
|
+
FileUtils.rm_rf(@pathname)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def write_file(path, content)
|
103
|
+
lockfile = Lockfile.new(path)
|
104
|
+
lockfile.hold_for_update
|
105
|
+
lockfile.write(content)
|
106
|
+
lockfile.write("\n")
|
107
|
+
lockfile.commit
|
108
|
+
end
|
109
|
+
|
110
|
+
def open_todo_file
|
111
|
+
return unless File.directory?(@pathname)
|
112
|
+
|
113
|
+
@todo_file = Lockfile.new(@todo_path)
|
114
|
+
@todo_file.hold_for_update
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
require_relative "./inspector"
|
4
|
+
require_relative "../sorted_hash"
|
5
|
+
|
6
|
+
class Repository
|
7
|
+
class Status
|
8
|
+
|
9
|
+
attr_reader :changed,
|
10
|
+
:stats,
|
11
|
+
:head_tree,
|
12
|
+
:index_changes,
|
13
|
+
:conflicts,
|
14
|
+
:workspace_changes,
|
15
|
+
:untracked_files
|
16
|
+
|
17
|
+
def initialize(repository, commit_oid = nil)
|
18
|
+
@repo = repository
|
19
|
+
@stats = {}
|
20
|
+
|
21
|
+
@inspector = Inspector.new(@repo)
|
22
|
+
|
23
|
+
@changed = SortedSet.new
|
24
|
+
@index_changes = SortedHash.new
|
25
|
+
@conflicts = SortedHash.new
|
26
|
+
@workspace_changes = SortedHash.new
|
27
|
+
@untracked_files = SortedSet.new
|
28
|
+
|
29
|
+
commit_oid ||= @repo.refs.read_head
|
30
|
+
@head_tree = @repo.database.load_tree_list(commit_oid)
|
31
|
+
|
32
|
+
scan_workspace
|
33
|
+
check_index_entries
|
34
|
+
collect_deleted_head_files
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def record_change(path, set, type)
|
40
|
+
@changed.add(path)
|
41
|
+
set[path] = type
|
42
|
+
end
|
43
|
+
|
44
|
+
def scan_workspace(prefix = nil)
|
45
|
+
@repo.workspace.list_dir(prefix).each do |path, stat|
|
46
|
+
if @repo.index.tracked?(path)
|
47
|
+
@stats[path] = stat if stat.file?
|
48
|
+
scan_workspace(path) if stat.directory?
|
49
|
+
elsif @inspector.trackable_file?(path, stat)
|
50
|
+
path += File::SEPARATOR if stat.directory?
|
51
|
+
@untracked_files.add(path)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_index_entries
|
57
|
+
@repo.index.each_entry do |entry|
|
58
|
+
if entry.stage == 0
|
59
|
+
check_index_against_workspace(entry)
|
60
|
+
check_index_against_head_tree(entry)
|
61
|
+
else
|
62
|
+
@changed.add(entry.path)
|
63
|
+
@conflicts[entry.path] ||= []
|
64
|
+
@conflicts[entry.path].push(entry.stage)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_index_against_workspace(entry)
|
70
|
+
stat = @stats[entry.path]
|
71
|
+
status = @inspector.compare_index_to_workspace(entry, stat)
|
72
|
+
|
73
|
+
if status
|
74
|
+
record_change(entry.path, @workspace_changes, status)
|
75
|
+
else
|
76
|
+
@repo.index.update_entry_stat(entry, stat)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_index_against_head_tree(entry)
|
81
|
+
item = @head_tree[entry.path]
|
82
|
+
status = @inspector.compare_tree_to_index(item, entry)
|
83
|
+
|
84
|
+
if status
|
85
|
+
record_change(entry.path, @index_changes, status)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def collect_deleted_head_files
|
90
|
+
@head_tree.each_key do |path|
|
91
|
+
unless @repo.index.tracked_file?(path)
|
92
|
+
record_change(path, @index_changes, :deleted)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/lib/rev_list.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
require_relative "./revision"
|
5
|
+
|
6
|
+
class RevList
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
RANGE = /^(.*)\.\.(.*)$/
|
10
|
+
EXCLUDE = /^\^(.+)$/
|
11
|
+
|
12
|
+
def initialize(repo, revs, options = {})
|
13
|
+
@repo = repo
|
14
|
+
@commits = {}
|
15
|
+
@flags = Hash.new { |hash, oid| hash[oid] = Set.new }
|
16
|
+
@queue = []
|
17
|
+
@limited = false
|
18
|
+
@prune = []
|
19
|
+
@diffs = {}
|
20
|
+
@output = []
|
21
|
+
@pending = []
|
22
|
+
@paths = {}
|
23
|
+
|
24
|
+
@objects = options.fetch(:objects, false)
|
25
|
+
@missing = options.fetch(:missing, false)
|
26
|
+
@walk = options.fetch(:walk, true)
|
27
|
+
|
28
|
+
include_refs(repo.refs.list_all_refs) if options[:all]
|
29
|
+
include_refs(repo.refs.list_branches) if options[:branches]
|
30
|
+
include_refs(repo.refs.list_remotes) if options[:remotes]
|
31
|
+
|
32
|
+
revs.each { |rev| handle_revision(rev) }
|
33
|
+
handle_revision(Revision::HEAD) if @queue.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def each
|
37
|
+
limit_list if @limited
|
38
|
+
mark_edges_uninteresting if @objects
|
39
|
+
traverse_commits { |commit| yield commit }
|
40
|
+
traverse_pending { |object| yield object, @paths[object.oid] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def tree_diff(old_oid, new_oid)
|
44
|
+
key = [old_oid, new_oid]
|
45
|
+
@diffs[key] ||= @repo.database.tree_diff(old_oid, new_oid, @prune)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def include_refs(refs)
|
51
|
+
oids = refs.map(&:read_oid).compact
|
52
|
+
oids.each { |oid| handle_revision(oid) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_revision(rev)
|
56
|
+
if @repo.workspace.stat_file(rev)
|
57
|
+
@prune.push(Pathname.new(rev))
|
58
|
+
elsif match = RANGE.match(rev)
|
59
|
+
set_start_point(match[1], false)
|
60
|
+
set_start_point(match[2], true)
|
61
|
+
@walk = true
|
62
|
+
elsif match = EXCLUDE.match(rev)
|
63
|
+
set_start_point(match[1], false)
|
64
|
+
@walk = true
|
65
|
+
else
|
66
|
+
set_start_point(rev, true)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_start_point(rev, interesting)
|
71
|
+
rev = Revision::HEAD if rev == ""
|
72
|
+
oid = Revision.new(@repo, rev).resolve(Revision::COMMIT)
|
73
|
+
|
74
|
+
commit = load_commit(oid)
|
75
|
+
enqueue_commit(commit)
|
76
|
+
|
77
|
+
unless interesting
|
78
|
+
@limited = true
|
79
|
+
mark(oid, :uninteresting)
|
80
|
+
mark_parents_uninteresting(commit)
|
81
|
+
end
|
82
|
+
|
83
|
+
rescue Revision::InvalidObject => error
|
84
|
+
raise error unless @missing
|
85
|
+
end
|
86
|
+
|
87
|
+
def enqueue_commit(commit)
|
88
|
+
return unless mark(commit.oid, :seen)
|
89
|
+
|
90
|
+
if @walk
|
91
|
+
index = @queue.find_index { |c| c.date < commit.date }
|
92
|
+
@queue.insert(index || @queue.size, commit)
|
93
|
+
else
|
94
|
+
@queue.push(commit)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def limit_list
|
99
|
+
while still_interesting?
|
100
|
+
commit = @queue.shift
|
101
|
+
add_parents(commit)
|
102
|
+
|
103
|
+
unless marked?(commit.oid, :uninteresting)
|
104
|
+
@output.push(commit)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
@queue = @output
|
109
|
+
end
|
110
|
+
|
111
|
+
def still_interesting?
|
112
|
+
return false if @queue.empty?
|
113
|
+
|
114
|
+
oldest_out = @output.last
|
115
|
+
newest_in = @queue.first
|
116
|
+
|
117
|
+
return true if oldest_out and oldest_out.date <= newest_in.date
|
118
|
+
|
119
|
+
if @queue.any? { |commit| not marked?(commit.oid, :uninteresting) }
|
120
|
+
return true
|
121
|
+
end
|
122
|
+
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_parents(commit)
|
127
|
+
return unless @walk and mark(commit.oid, :added)
|
128
|
+
|
129
|
+
if marked?(commit.oid, :uninteresting)
|
130
|
+
parents = commit.parents.map { |oid| load_commit(oid) }
|
131
|
+
parents.each { |parent| mark_parents_uninteresting(parent) }
|
132
|
+
else
|
133
|
+
parents = simplify_commit(commit).map { |oid| load_commit(oid) }
|
134
|
+
end
|
135
|
+
|
136
|
+
parents.each { |parent| enqueue_commit(parent) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def mark_parents_uninteresting(commit)
|
140
|
+
queue = commit.parents.clone
|
141
|
+
|
142
|
+
until queue.empty?
|
143
|
+
oid = queue.shift
|
144
|
+
|
145
|
+
while oid
|
146
|
+
break unless mark(oid, :uninteresting)
|
147
|
+
|
148
|
+
parent = @commits[oid]
|
149
|
+
break unless parent
|
150
|
+
|
151
|
+
oid = parent.parents.first
|
152
|
+
queue.concat(parent.parents.drop(1))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def mark_edges_uninteresting
|
158
|
+
@queue.each do |commit|
|
159
|
+
if marked?(commit.oid, :uninteresting)
|
160
|
+
mark_tree_uninteresting(commit.tree)
|
161
|
+
end
|
162
|
+
|
163
|
+
commit.parents.each do |oid|
|
164
|
+
next unless marked?(oid, :uninteresting)
|
165
|
+
|
166
|
+
parent = load_commit(oid)
|
167
|
+
mark_tree_uninteresting(parent.tree)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def mark_tree_uninteresting(tree_oid)
|
173
|
+
entry = @repo.database.tree_entry(tree_oid)
|
174
|
+
traverse_tree(entry) { |object| mark(object.oid, :uninteresting) }
|
175
|
+
end
|
176
|
+
|
177
|
+
def simplify_commit(commit)
|
178
|
+
return commit.parents if @prune.empty?
|
179
|
+
|
180
|
+
parents = commit.parents
|
181
|
+
parents = [nil] if parents.empty?
|
182
|
+
|
183
|
+
parents.each do |oid|
|
184
|
+
next unless tree_diff(oid, commit.oid).empty?
|
185
|
+
mark(commit.oid, :treesame)
|
186
|
+
return [*oid]
|
187
|
+
end
|
188
|
+
|
189
|
+
commit.parents
|
190
|
+
end
|
191
|
+
|
192
|
+
def traverse_commits
|
193
|
+
until @queue.empty?
|
194
|
+
commit = @queue.shift
|
195
|
+
add_parents(commit) unless @limited
|
196
|
+
|
197
|
+
next if marked?(commit.oid, :uninteresting)
|
198
|
+
next if marked?(commit.oid, :treesame)
|
199
|
+
|
200
|
+
@pending.push(@repo.database.tree_entry(commit.tree))
|
201
|
+
yield commit
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def traverse_pending
|
206
|
+
return unless @objects
|
207
|
+
|
208
|
+
@pending.each do |entry|
|
209
|
+
traverse_tree(entry) do |object|
|
210
|
+
next if marked?(object.oid, :uninteresting)
|
211
|
+
next unless mark(object.oid, :seen)
|
212
|
+
|
213
|
+
yield object
|
214
|
+
true
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def traverse_tree(entry, path = Pathname.new(""))
|
220
|
+
@paths[entry.oid] ||= path
|
221
|
+
|
222
|
+
return unless yield entry
|
223
|
+
return unless entry.tree?
|
224
|
+
|
225
|
+
tree = @repo.database.load(entry.oid)
|
226
|
+
|
227
|
+
tree.each_entry do |name, item|
|
228
|
+
traverse_tree(item, path.join(name)) { |object| yield object }
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def load_commit(oid)
|
233
|
+
return nil unless oid
|
234
|
+
@commits[oid] ||= @repo.database.load(oid)
|
235
|
+
end
|
236
|
+
|
237
|
+
def mark(oid, flag)
|
238
|
+
@flags[oid].add?(flag)
|
239
|
+
end
|
240
|
+
|
241
|
+
def marked?(oid, flag)
|
242
|
+
@flags[oid].include?(flag)
|
243
|
+
end
|
244
|
+
end
|