Show More
@@ -1,152 +1,122 b'' | |||
|
1 | 1 | // dirstate.rs |
|
2 | 2 | // |
|
3 | 3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> |
|
4 | 4 | // |
|
5 | 5 | // This software may be used and distributed according to the terms of the |
|
6 | 6 | // GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | //! Bindings for the `hg::dirstate` module provided by the |
|
9 | 9 | //! `hg-core` package. |
|
10 | 10 | //! |
|
11 | 11 | //! From Python, this will be seen as `mercurial.rustext.dirstate` |
|
12 | 12 | mod copymap; |
|
13 | 13 | mod dirs_multiset; |
|
14 | 14 | mod dirstate_map; |
|
15 | 15 | mod non_normal_entries; |
|
16 | 16 | mod status; |
|
17 | 17 | use crate::{ |
|
18 | 18 | dirstate::{ |
|
19 | 19 | dirs_multiset::Dirs, dirstate_map::DirstateMap, status::status_wrapper, |
|
20 | 20 | }, |
|
21 | 21 | exceptions, |
|
22 | 22 | }; |
|
23 | 23 | use cpython::{ |
|
24 |
|
|
|
25 | PySequence, Python, | |
|
24 | PyBytes, PyDict, PyErr, PyList, PyModule, PyObject, PyResult, Python, | |
|
26 | 25 | }; |
|
27 | 26 | use hg::dirstate_tree::on_disk::V2_FORMAT_MARKER; |
|
28 | use hg::{utils::hg_path::HgPathBuf, DirstateEntry, EntryState, StateMap}; | |
|
27 | use hg::DirstateEntry; | |
|
29 | 28 | use libc::{c_char, c_int}; |
|
30 | use std::convert::TryFrom; | |
|
31 | 29 | |
|
32 | 30 | // C code uses a custom `dirstate_tuple` type, checks in multiple instances |
|
33 | 31 | // for this type, and raises a Python `Exception` if the check does not pass. |
|
34 | 32 | // Because this type differs only in name from the regular Python tuple, it |
|
35 | 33 | // would be a good idea in the near future to remove it entirely to allow |
|
36 | 34 | // for a pure Python tuple of the same effective structure to be used, |
|
37 | 35 | // rendering this type and the capsule below useless. |
|
38 | 36 | py_capsule_fn!( |
|
39 | 37 | from mercurial.cext.parsers import make_dirstate_item_CAPI |
|
40 | 38 | as make_dirstate_item_capi |
|
41 | 39 | signature ( |
|
42 | 40 | state: c_char, |
|
43 | 41 | mode: c_int, |
|
44 | 42 | size: c_int, |
|
45 | 43 | mtime: c_int, |
|
46 | 44 | ) -> *mut RawPyObject |
|
47 | 45 | ); |
|
48 | 46 | |
|
49 | 47 | pub fn make_dirstate_item( |
|
50 | 48 | py: Python, |
|
51 | 49 | entry: &DirstateEntry, |
|
52 | 50 | ) -> PyResult<PyObject> { |
|
53 | 51 | let &DirstateEntry { |
|
54 | 52 | state, |
|
55 | 53 | mode, |
|
56 | 54 | size, |
|
57 | 55 | mtime, |
|
58 | 56 | } = entry; |
|
59 | 57 | // Explicitly go through u8 first, then cast to platform-specific `c_char` |
|
60 | 58 | // because Into<u8> has a specific implementation while `as c_char` would |
|
61 | 59 | // just do a naive enum cast. |
|
62 | 60 | let state_code: u8 = state.into(); |
|
63 | 61 | make_dirstate_item_raw(py, state_code, mode, size, mtime) |
|
64 | 62 | } |
|
65 | 63 | |
|
66 | 64 | pub fn make_dirstate_item_raw( |
|
67 | 65 | py: Python, |
|
68 | 66 | state: u8, |
|
69 | 67 | mode: i32, |
|
70 | 68 | size: i32, |
|
71 | 69 | mtime: i32, |
|
72 | 70 | ) -> PyResult<PyObject> { |
|
73 | 71 | let make = make_dirstate_item_capi::retrieve(py)?; |
|
74 | 72 | let maybe_obj = unsafe { |
|
75 | 73 | let ptr = make(state as c_char, mode, size, mtime); |
|
76 | 74 | PyObject::from_owned_ptr_opt(py, ptr) |
|
77 | 75 | }; |
|
78 | 76 | maybe_obj.ok_or_else(|| PyErr::fetch(py)) |
|
79 | 77 | } |
|
80 | 78 | |
|
81 | pub fn extract_dirstate(py: Python, dmap: &PyDict) -> Result<StateMap, PyErr> { | |
|
82 | dmap.items(py) | |
|
83 | .iter() | |
|
84 | .map(|(filename, stats)| { | |
|
85 | let stats = stats.extract::<PySequence>(py)?; | |
|
86 | let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?; | |
|
87 | let state = | |
|
88 | EntryState::try_from(state.data(py)[0]).map_err(|e| { | |
|
89 | PyErr::new::<exc::ValueError, _>(py, e.to_string()) | |
|
90 | })?; | |
|
91 | let mode = stats.get_item(py, 1)?.extract(py)?; | |
|
92 | let size = stats.get_item(py, 2)?.extract(py)?; | |
|
93 | let mtime = stats.get_item(py, 3)?.extract(py)?; | |
|
94 | let filename = filename.extract::<PyBytes>(py)?; | |
|
95 | let filename = filename.data(py); | |
|
96 | Ok(( | |
|
97 | HgPathBuf::from(filename.to_owned()), | |
|
98 | DirstateEntry { | |
|
99 | state, | |
|
100 | mode, | |
|
101 | size, | |
|
102 | mtime, | |
|
103 | }, | |
|
104 | )) | |
|
105 | }) | |
|
106 | .collect() | |
|
107 | } | |
|
108 | ||
|
109 | 79 | /// Create the module, with `__package__` given from parent |
|
110 | 80 | pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { |
|
111 | 81 | let dotted_name = &format!("{}.dirstate", package); |
|
112 | 82 | let m = PyModule::new(py, dotted_name)?; |
|
113 | 83 | |
|
114 | 84 | env_logger::init(); |
|
115 | 85 | |
|
116 | 86 | m.add(py, "__package__", package)?; |
|
117 | 87 | m.add(py, "__doc__", "Dirstate - Rust implementation")?; |
|
118 | 88 | |
|
119 | 89 | m.add( |
|
120 | 90 | py, |
|
121 | 91 | "FallbackError", |
|
122 | 92 | py.get_type::<exceptions::FallbackError>(), |
|
123 | 93 | )?; |
|
124 | 94 | m.add_class::<Dirs>(py)?; |
|
125 | 95 | m.add_class::<DirstateMap>(py)?; |
|
126 | 96 | m.add(py, "V2_FORMAT_MARKER", PyBytes::new(py, V2_FORMAT_MARKER))?; |
|
127 | 97 | m.add( |
|
128 | 98 | py, |
|
129 | 99 | "status", |
|
130 | 100 | py_fn!( |
|
131 | 101 | py, |
|
132 | 102 | status_wrapper( |
|
133 | 103 | dmap: DirstateMap, |
|
134 | 104 | root_dir: PyObject, |
|
135 | 105 | matcher: PyObject, |
|
136 | 106 | ignorefiles: PyList, |
|
137 | 107 | check_exec: bool, |
|
138 | 108 | last_normal_time: i64, |
|
139 | 109 | list_clean: bool, |
|
140 | 110 | list_ignored: bool, |
|
141 | 111 | list_unknown: bool, |
|
142 | 112 | collect_traversed_dirs: bool |
|
143 | 113 | ) |
|
144 | 114 | ), |
|
145 | 115 | )?; |
|
146 | 116 | |
|
147 | 117 | let sys = PyModule::import(py, "sys")?; |
|
148 | 118 | let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; |
|
149 | 119 | sys_modules.set_item(py, dotted_name, &m)?; |
|
150 | 120 | |
|
151 | 121 | Ok(m) |
|
152 | 122 | } |
@@ -1,134 +1,124 b'' | |||
|
1 | 1 | // dirs_multiset.rs |
|
2 | 2 | // |
|
3 | 3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> |
|
4 | 4 | // |
|
5 | 5 | // This software may be used and distributed according to the terms of the |
|
6 | 6 | // GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the |
|
9 | 9 | //! `hg-core` package. |
|
10 | 10 | |
|
11 | 11 | use std::cell::RefCell; |
|
12 | 12 | |
|
13 | 13 | use cpython::{ |
|
14 |
exc, ObjectProtocol |
|
|
15 |
|
|
|
14 | exc, ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult, | |
|
15 | Python, UnsafePyLeaked, | |
|
16 | 16 | }; |
|
17 | 17 | |
|
18 | use crate::dirstate::extract_dirstate; | |
|
19 | 18 | use hg::{ |
|
20 | 19 | utils::hg_path::{HgPath, HgPathBuf}, |
|
21 |
DirsMultiset, DirsMultisetIter, |
|
|
20 | DirsMultiset, DirsMultisetIter, DirstateMapError, | |
|
22 | 21 | }; |
|
23 | 22 | |
|
24 | 23 | py_class!(pub class Dirs |py| { |
|
25 | 24 | @shared data inner: DirsMultiset; |
|
26 | 25 | |
|
27 | 26 | // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes |
|
28 | 27 | // a `list`) |
|
29 | 28 | def __new__( |
|
30 | 29 | _cls, |
|
31 | 30 | map: PyObject, |
|
32 | only_tracked: Option<PyObject> = None | |
|
33 | 31 | ) -> PyResult<Self> { |
|
34 | let only_tracked_b = if let Some(only_tracked) = only_tracked { | |
|
35 | only_tracked.extract::<PyBool>(py)?.is_true() | |
|
36 | } else { | |
|
37 | false | |
|
38 | }; | |
|
39 | let inner = if let Ok(map) = map.cast_as::<PyDict>(py) { | |
|
40 | let dirstate = extract_dirstate(py, &map)?; | |
|
41 | let dirstate = dirstate.iter().map(|(k, v)| Ok((k, *v))); | |
|
42 | DirsMultiset::from_dirstate(dirstate, only_tracked_b) | |
|
43 | .map_err(|e: DirstateError| { | |
|
44 | PyErr::new::<exc::ValueError, _>(py, e.to_string()) | |
|
45 | })? | |
|
32 | let inner = if map.cast_as::<PyDict>(py).is_ok() { | |
|
33 | let err = "pathutil.dirs() with a dict should only be used by the Python dirstatemap \ | |
|
34 | and should not be used when Rust is enabled"; | |
|
35 | return Err(PyErr::new::<exc::TypeError, _>(py, err.to_string())) | |
|
46 | 36 | } else { |
|
47 | 37 | let map: Result<Vec<HgPathBuf>, PyErr> = map |
|
48 | 38 | .iter(py)? |
|
49 | 39 | .map(|o| { |
|
50 | 40 | Ok(HgPathBuf::from_bytes( |
|
51 | 41 | o?.extract::<PyBytes>(py)?.data(py), |
|
52 | 42 | )) |
|
53 | 43 | }) |
|
54 | 44 | .collect(); |
|
55 | 45 | DirsMultiset::from_manifest(&map?) |
|
56 | 46 | .map_err(|e| { |
|
57 | 47 | PyErr::new::<exc::ValueError, _>(py, e.to_string()) |
|
58 | 48 | })? |
|
59 | 49 | }; |
|
60 | 50 | |
|
61 | 51 | Self::create_instance(py, inner) |
|
62 | 52 | } |
|
63 | 53 | |
|
64 | 54 | def addpath(&self, path: PyObject) -> PyResult<PyObject> { |
|
65 | 55 | self.inner(py).borrow_mut().add_path( |
|
66 | 56 | HgPath::new(path.extract::<PyBytes>(py)?.data(py)), |
|
67 | 57 | ).and(Ok(py.None())).or_else(|e| { |
|
68 | 58 | match e { |
|
69 | 59 | DirstateMapError::EmptyPath => { |
|
70 | 60 | Ok(py.None()) |
|
71 | 61 | }, |
|
72 | 62 | e => { |
|
73 | 63 | Err(PyErr::new::<exc::ValueError, _>( |
|
74 | 64 | py, |
|
75 | 65 | e.to_string(), |
|
76 | 66 | )) |
|
77 | 67 | } |
|
78 | 68 | } |
|
79 | 69 | }) |
|
80 | 70 | } |
|
81 | 71 | |
|
82 | 72 | def delpath(&self, path: PyObject) -> PyResult<PyObject> { |
|
83 | 73 | self.inner(py).borrow_mut().delete_path( |
|
84 | 74 | HgPath::new(path.extract::<PyBytes>(py)?.data(py)), |
|
85 | 75 | ) |
|
86 | 76 | .and(Ok(py.None())) |
|
87 | 77 | .or_else(|e| { |
|
88 | 78 | match e { |
|
89 | 79 | DirstateMapError::EmptyPath => { |
|
90 | 80 | Ok(py.None()) |
|
91 | 81 | }, |
|
92 | 82 | e => { |
|
93 | 83 | Err(PyErr::new::<exc::ValueError, _>( |
|
94 | 84 | py, |
|
95 | 85 | e.to_string(), |
|
96 | 86 | )) |
|
97 | 87 | } |
|
98 | 88 | } |
|
99 | 89 | }) |
|
100 | 90 | } |
|
101 | 91 | def __iter__(&self) -> PyResult<DirsMultisetKeysIterator> { |
|
102 | 92 | let leaked_ref = self.inner(py).leak_immutable(); |
|
103 | 93 | DirsMultisetKeysIterator::from_inner( |
|
104 | 94 | py, |
|
105 | 95 | unsafe { leaked_ref.map(py, |o| o.iter()) }, |
|
106 | 96 | ) |
|
107 | 97 | } |
|
108 | 98 | |
|
109 | 99 | def __contains__(&self, item: PyObject) -> PyResult<bool> { |
|
110 | 100 | Ok(self.inner(py).borrow().contains(HgPath::new( |
|
111 | 101 | item.extract::<PyBytes>(py)?.data(py).as_ref(), |
|
112 | 102 | ))) |
|
113 | 103 | } |
|
114 | 104 | }); |
|
115 | 105 | |
|
116 | 106 | impl Dirs { |
|
117 | 107 | pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> { |
|
118 | 108 | Self::create_instance(py, d) |
|
119 | 109 | } |
|
120 | 110 | |
|
121 | 111 | fn translate_key( |
|
122 | 112 | py: Python, |
|
123 | 113 | res: &HgPathBuf, |
|
124 | 114 | ) -> PyResult<Option<PyBytes>> { |
|
125 | 115 | Ok(Some(PyBytes::new(py, res.as_bytes()))) |
|
126 | 116 | } |
|
127 | 117 | } |
|
128 | 118 | |
|
129 | 119 | py_shared_iterator!( |
|
130 | 120 | DirsMultisetKeysIterator, |
|
131 | 121 | UnsafePyLeaked<DirsMultisetIter<'static>>, |
|
132 | 122 | Dirs::translate_key, |
|
133 | 123 | Option<PyBytes> |
|
134 | 124 | ); |
@@ -1,27 +1,27 b'' | |||
|
1 | 1 | from __future__ import absolute_import |
|
2 | 2 | |
|
3 | 3 | import unittest |
|
4 | 4 | |
|
5 | 5 | import silenttestrunner |
|
6 | 6 | |
|
7 | 7 | from mercurial import pathutil |
|
8 | 8 | |
|
9 | 9 | |
|
10 | 10 | class dirstests(unittest.TestCase): |
|
11 | 11 | def testdirs(self): |
|
12 | 12 | for case, want in [ |
|
13 | 13 | (b'a/a/a', [b'a', b'a/a', b'']), |
|
14 | 14 | (b'alpha/beta/gamma', [b'', b'alpha', b'alpha/beta']), |
|
15 | 15 | ]: |
|
16 |
d = pathutil.dirs( |
|
|
16 | d = pathutil.dirs([]) | |
|
17 | 17 | d.addpath(case) |
|
18 | 18 | self.assertEqual(sorted(d), sorted(want)) |
|
19 | 19 | |
|
20 | 20 | def testinvalid(self): |
|
21 | 21 | with self.assertRaises(ValueError): |
|
22 |
d = pathutil.dirs( |
|
|
22 | d = pathutil.dirs([]) | |
|
23 | 23 | d.addpath(b'a//b') |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | if __name__ == '__main__': |
|
27 | 27 | silenttestrunner.main(__name__) |
General Comments 0
You need to be logged in to leave comments.
Login now