jit 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|