Skip to content

Commit 29c311d

Browse files
author
Robin Luckey
committed
OTWO-415 First pass at using python library for hg cat
1 parent f0e7ea6 commit 29c311d

File tree

8 files changed

+224
-0
lines changed

8 files changed

+224
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.swp
22
*.pyc
33
pkg/
4+
*.cache

lib/scm.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module Scm
1616
require 'lib/scm/adapters/svn_chain_adapter'
1717
require 'lib/scm/adapters/git_adapter'
1818
require 'lib/scm/adapters/hg_adapter'
19+
require 'lib/scm/adapters/hglib_adapter'
1920
require 'lib/scm/adapters/bzr_adapter'
2021
require 'lib/scm/adapters/bzrlib_adapter'
2122
require 'lib/scm/adapters/factory'

lib/scm/adapters/hglib/cat_file.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Scm::Adapters
2+
class HglibAdapter < HgAdapter
3+
4+
def cat(revision, path)
5+
hg_client.cat_file(revision, path)
6+
end
7+
8+
end
9+
end

lib/scm/adapters/hglib/client.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require 'rubygems'
2+
require 'open4'
3+
4+
class HglibClient
5+
def initialize(repository_url)
6+
@repository_url = repository_url
7+
@py_script = File.dirname(__FILE__) + '/server.py'
8+
end
9+
10+
def start
11+
@pid, @stdin, @stdout, @stderr = Open4::popen4 "python #{@py_script}"
12+
open_repository
13+
end
14+
15+
def open_repository
16+
send_command("REPO_OPEN\t#{@repository_url}")
17+
end
18+
19+
def cat_file(revision, file)
20+
send_command("CAT_FILE\t#{revision}\t#{file}")
21+
end
22+
23+
def parent_tokens(revision)
24+
send_command("PARENT_TOKENS\t#{revision}").split("\t")
25+
end
26+
27+
def send_command(cmd)
28+
# send the command
29+
@stdin.puts cmd
30+
@stdin.flush
31+
32+
# get status on stderr, first letter indicates state,
33+
# remaing value indicates length of the file content
34+
status = @stderr.read(10)
35+
flag = status[0,1]
36+
size = status[1,9].to_i
37+
if flag == 'F'
38+
return nil
39+
elsif flag == 'E'
40+
error = @stdout.read(size)
41+
raise RuntimeError.new("Exception in server process\n#{error}")
42+
end
43+
44+
# read content from stdout
45+
return @stdout.read(size)
46+
end
47+
48+
def shutdown
49+
send_command("QUIT")
50+
Process.waitpid(@pid, Process::WNOHANG)
51+
end
52+
end

lib/scm/adapters/hglib/server.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import sys
2+
import time
3+
import traceback
4+
5+
from mercurial import ui, hg
6+
7+
class HglibPipeServer:
8+
def __init__(self, repository_url):
9+
self.ui = ui.ui()
10+
self.repository = hg.repository(self.ui, repository_url)
11+
12+
def get_file_content(self, filename, revision):
13+
c = self.repository.changectx(revision)
14+
fc = c[filename]
15+
contents = fc.data()
16+
return contents
17+
18+
def get_parent_tokens(self, revision):
19+
return None
20+
21+
class Command:
22+
def __init__(self, line):
23+
self.args = line.rstrip().split('\t')
24+
25+
def get_action(self):
26+
return self.args[0]
27+
28+
def get_arg(self, num):
29+
return self.args[num]
30+
31+
def send_status(code, data_len):
32+
sys.stderr.write('%s%09d' % (code, data_len))
33+
sys.stderr.flush()
34+
35+
def send_success(data_len=0):
36+
send_status('T', data_len)
37+
38+
def send_failure(data_len=0):
39+
send_status('F', data_len)
40+
41+
def send_error(data_len=0):
42+
send_status('E', data_len)
43+
44+
def send_data(result):
45+
sys.stdout.write(result)
46+
sys.stdout.flush()
47+
48+
def exit_delayed(status, delay=1):
49+
time.sleep(delay)
50+
sys.exit(status)
51+
52+
def command_loop():
53+
while True:
54+
cmd = Command(sys.stdin.readline())
55+
if cmd.get_action() == 'REPO_OPEN':
56+
commander = HglibPipeServer(cmd.get_arg(1))
57+
send_success()
58+
elif cmd.get_action() == 'CAT_FILE':
59+
content = commander.get_file_content(cmd.get_arg(2), cmd.get_arg(1))
60+
if content == None:
61+
send_failure()
62+
else:
63+
send_success(len(content))
64+
send_data(content)
65+
elif cmd.get_action() == 'PARENT_TOKENS':
66+
tokens = commander.get_parent_tokens(cmd.get_arg(1))
67+
tokens = '|'.join(tokens)
68+
send_success(len(tokens))
69+
send_data(tokens)
70+
elif cmd.get_action() == 'QUIT':
71+
send_success()
72+
exit_delayed(status=0)
73+
else:
74+
error = "Invalid Command - %s" % cmd.get_action()
75+
send_error(len(error))
76+
send_data(error)
77+
exit_delayed(status=1)
78+
79+
if __name__ == "__main__":
80+
try:
81+
command_loop()
82+
except:
83+
exc_trace = traceback.format_exc()
84+
send_error(len(exc_trace))
85+
send_data(exc_trace)
86+
exit_delayed(status=1)

lib/scm/adapters/hglib_adapter.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'rubygems'
2+
require 'lib/scm/adapters/hglib/client'
3+
4+
module Scm::Adapters
5+
class HglibAdapter < HgAdapter
6+
7+
def setup
8+
hg_client = HglibClient.new(url)
9+
hg_client.start
10+
hg_client
11+
end
12+
13+
def hg_client
14+
@hg_client ||= setup
15+
end
16+
17+
def cleanup
18+
@hg_client && @hg_client.shutdown
19+
end
20+
21+
end
22+
end
23+
24+
require 'lib/scm/adapters/hglib/cat_file'

test/test_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ def with_hg_repository(name)
9393
with_repository(Scm::Adapters::HgAdapter, name) { |hg| yield hg }
9494
end
9595

96+
def with_hglib_repository(name)
97+
with_repository(Scm::Adapters::HglibAdapter, name) { |hg| yield hg }
98+
end
99+
96100
def with_bzr_repository(name)
97101
with_repository(Scm::Adapters::BzrAdapter, name) { |bzr| yield bzr }
98102
end

test/unit/hglib_cat_file_test.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
require File.dirname(__FILE__) + '/../test_helper'
2+
3+
module Scm::Adapters
4+
class HglibCatFileTest < Scm::Test
5+
6+
def test_cat_file
7+
with_hglib_repository('hg') do |hg|
8+
expected = <<-EXPECTED
9+
/* Hello, World! */
10+
11+
/*
12+
* This file is not covered by any license, especially not
13+
* the GNU General Public License (GPL). Have fun!
14+
*/
15+
16+
#include <stdio.h>
17+
main()
18+
{
19+
printf("Hello, World!\\n");
20+
}
21+
EXPECTED
22+
23+
# The file was deleted in revision 468336c6671c. Check that it does not exist now, but existed in parent.
24+
# assert_equal nil, hg.cat_file(Scm::Commit.new(:token => '75532c1e1f1d'), Scm::Diff.new(:path => 'helloworld.c'))
25+
assert_equal expected, hg.cat_file_parent(Scm::Commit.new(:token => '75532c1e1f1d'), Scm::Diff.new(:path => 'helloworld.c'))
26+
assert_equal expected, hg.cat_file(Scm::Commit.new(:token => '468336c6671c'), Scm::Diff.new(:path => 'helloworld.c'))
27+
end
28+
end
29+
30+
# Ensure that we escape bash-significant characters like ' and & when they appear in the filename
31+
def test_funny_file_name_chars
32+
Scm::ScratchDir.new do |dir|
33+
# Make a file with a problematic filename
34+
funny_name = '#|file_name` $(&\'")#'
35+
File.open(File.join(dir, funny_name), 'w') { |f| f.write "contents" }
36+
37+
# Add it to an hg repository
38+
`cd #{dir} && hg init && hg add * && hg commit -m test`
39+
40+
# Confirm that we can read the file back
41+
hg = HglibAdapter.new(:url => dir).normalize
42+
assert_equal "contents", hg.cat_file(hg.head, Scm::Diff.new(:path => funny_name))
43+
end
44+
end
45+
46+
end
47+
end

0 commit comments

Comments
 (0)