D7517: filemerge: byteify the open() mode

2020-02-08 Thread mharbison72 (Matt Harbison)
mharbison72 added a comment.
mharbison72 abandoned this revision.


  The py3 breakage fixed by D8099  
convinced me that this is going in the wrong direction.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7517/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7517

To: mharbison72, #hg-reviewers, marmoute
Cc: marmoute, durin42, dlax, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D8099: lfs: use str for the open() mode when opening a blob for py3

2020-02-08 Thread mharbison72 (Matt Harbison)
mharbison72 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The other fix for this was to leave the mode as bytes, and import
  `pycompat.open()` like a bunch of other modules do.  But I think it's 
confusing
  to still use bytes at the python boundary, and obviously error prone.  
Grepping
  for ` open\(.+, ['"][a-z]+['"]\)` and ` open\(.+, b['"][a-z]+['"]\)` outside 
of
  `tests`, there are 51 and 87 uses respectively, so it's not like this is a 
rare
  direct usage.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D8099

AFFECTED FILES
  hgext/lfs/blobstore.py

CHANGE DETAILS

diff --git a/hgext/lfs/blobstore.py b/hgext/lfs/blobstore.py
--- a/hgext/lfs/blobstore.py
+++ b/hgext/lfs/blobstore.py
@@ -128,7 +128,7 @@
 def open(self, oid):
 """Open a read-only file descriptor to the named blob, in either the
 usercache or the local store."""
-return open(self.path(oid), b'rb')
+return open(self.path(oid), 'rb')
 
 def path(self, oid):
 """Build the path for the given blob ``oid``.



To: mharbison72, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D8096: purge: add -i flag to delete ignored files instead of untracked files

2020-02-08 Thread valentin.gatienbaron (Valentin Gatien-Baron)
valentin.gatienbaron updated this revision to Diff 20031.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8096?vs=20023=20031

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D8096/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D8096

AFFECTED FILES
  hgext/purge.py
  mercurial/merge.py
  tests/test-purge.t

CHANGE DETAILS

diff --git a/tests/test-purge.t b/tests/test-purge.t
--- a/tests/test-purge.t
+++ b/tests/test-purge.t
@@ -120,19 +120,32 @@
   directory/untracked_file
   $ rm directory/untracked_file
 
-skip ignored files if --all not specified
+skip ignored files if -i or --all not specified
 
   $ touch ignored
   $ hg purge -p
   $ hg purge -v
+  $ touch untracked_file
   $ ls
   directory
   ignored
   r1
+  untracked_file
+  $ hg purge -p -i
+  ignored
+  $ hg purge -v -i
+  removing file ignored
+  $ ls
+  directory
+  r1
+  untracked_file
+  $ touch ignored
   $ hg purge -p --all
   ignored
+  untracked_file
   $ hg purge -v --all
   removing file ignored
+  removing file untracked_file
   $ ls
   directory
   r1
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -2698,6 +2698,7 @@
 def purge(
 repo,
 matcher,
+unknown=True,
 ignored=False,
 removeemptydirs=True,
 removefiles=True,
@@ -2709,7 +2710,9 @@
 ``matcher`` is a matcher configured to scan the working directory -
 potentially a subset.
 
-``ignored`` controls whether ignored files should also be purged.
+``unknown`` controls whether unknown files should be purged.
+
+``ignored`` controls whether ignored files should be purged.
 
 ``removeemptydirs`` controls whether empty directories should be removed.
 
@@ -2746,7 +2749,7 @@
 directories = []
 matcher.traversedir = directories.append
 
-status = repo.status(match=matcher, ignored=ignored, unknown=True)
+status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
 
 if removefiles:
 for f in sorted(status.unknown + status.ignored):
diff --git a/hgext/purge.py b/hgext/purge.py
--- a/hgext/purge.py
+++ b/hgext/purge.py
@@ -48,6 +48,7 @@
 [
 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
 (b'', b'all', None, _(b'purge ignored files too')),
+(b'i', b'ignored', None, _(b'purge only ignored files')),
 (b'', b'dirs', None, _(b'purge empty directories')),
 (b'', b'files', None, _(b'purge files')),
 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
@@ -80,7 +81,7 @@
 But it will leave untouched:
 
 - Modified and unmodified tracked files
-- Ignored files (unless --all is specified)
+- Ignored files (unless -i or --all is specified)
 - New files added to the repository (with :hg:`add`)
 
 The --files and --dirs options can be used to direct purge to delete
@@ -102,6 +103,13 @@
 if opts.get(b'print0'):
 eol = b'\0'
 act = False  # --print0 implies --print
+if opts.get(b'all', False):
+ignored = True
+unknown = True
+else:
+ignored = opts.get(b'ignored', False)
+unknown = not ignored
+cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
 
 removefiles = opts.get(b'files')
 removedirs = opts.get(b'dirs')
@@ -115,7 +123,8 @@
 paths = mergemod.purge(
 repo,
 match,
-ignored=opts.get(b'all', False),
+unknown=unknown,
+ignored=ignored,
 removeemptydirs=removedirs,
 removefiles=removefiles,
 abortonerror=opts.get(b'abort_on_err'),



To: valentin.gatienbaron, #hg-reviewers, marmoute
Cc: marmoute, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7819: rust-nodemap: core implementation for shortest

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute updated this revision to Diff 20028.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7819?vs=19640=20028

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7819/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7819

AFFECTED FILES
  rust/hg-core/src/revlog/node.rs
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -17,6 +17,7 @@
 RevlogIndex, NULL_REVISION,
 };
 
+use std::cmp::max;
 use std::fmt;
 use std::mem;
 use std::ops::Deref;
@@ -98,6 +99,42 @@
 ) -> Result, NodeMapError> {
 self.find_bin(idx, NodePrefix::from_hex(prefix)?.borrow())
 }
+
+/// Give the size of the shortest node prefix that determines
+/// the revision uniquely.
+///
+/// From a binary node prefix, if it is matched in the node map, this
+/// returns the number of hexadecimal digits that would had sufficed
+/// to find the revision uniquely.
+///
+/// Returns `None` if no `Revision` could be found for the prefix.
+///
+/// If several Revisions match the given prefix, a [`MultipleResults`]
+/// error is returned.
+fn unique_prefix_len_bin<'a>(
+,
+idx:  RevlogIndex,
+node_prefix: NodePrefixRef<'a>,
+) -> Result, NodeMapError>;
+
+/// Same as `unique_prefix_len_bin`, with the hexadecimal representation
+/// of the prefix as input.
+fn unique_prefix_len_hex(
+,
+idx:  RevlogIndex,
+prefix: ,
+) -> Result, NodeMapError> {
+self.unique_prefix_len_bin(idx, NodePrefix::from_hex(prefix)?.borrow())
+}
+
+/// Same as `unique_prefix_len_bin`, with a full `Node` as input
+fn unique_prefix_len_node(
+,
+idx:  RevlogIndex,
+node: ,
+) -> Result, NodeMapError> {
+self.unique_prefix_len_bin(idx, node.into())
+}
 }
 
 pub trait MutableNodeMap: NodeMap {
@@ -259,20 +296,24 @@
 fn validate_candidate<'p>(
 idx:  RevlogIndex,
 prefix: NodePrefixRef<'p>,
-rev: Option,
-) -> Result, NodeMapError> {
-if prefix.is_prefix_of(_NODE) {
-// NULL_REVISION always matches a prefix made only of zeros
+candidate: (Option, usize),
+) -> Result<(Option, usize), NodeMapError> {
+let (rev, steps) = candidate;
+if let Some(nz_nybble) = prefix.first_different_nybble(_NODE) {
+rev.map_or(Ok((None, steps)), |r| {
+has_prefix_or_none(idx, prefix, r)
+.map(|opt| (opt, max(steps, nz_nybble + 1)))
+})
+} else {
+// the prefix is only made of zeros; NULL_REVISION always matches it
 // and any other *valid* result is an ambiguity
 match rev {
-None => Ok(Some(NULL_REVISION)),
+None => Ok((Some(NULL_REVISION), steps + 1)),
 Some(r) => match has_prefix_or_none(idx, prefix, r)? {
-None => Ok(Some(NULL_REVISION)),
+None => Ok((Some(NULL_REVISION), steps + 1)),
 _ => Err(NodeMapError::MultipleResults),
 },
 }
-} else {
-rev.map_or(Ok(None), |r| has_prefix_or_none(idx, prefix, r))
 }
 }
 
@@ -356,13 +397,26 @@
 }
 
 /// Main working method for `NodeTree` searches
+///
+/// The first returned value is the result of analysing `NodeTree` data
+/// *alone*: whereas `None` guarantees that the given prefix is absent
+/// from the `NodeTree` data (but still could match `NULL_NODE`), with
+/// `Some(rev)`, it is to be understood that `rev` is the unique `Revision`
+/// that could match the prefix. Actually, all that can be inferred from
+/// the `NodeTree` data is that `rev` is the revision with the longest
+/// common node prefix with the given prefix.
+///
+/// The second returned value is the size of the smallest subprefix
+/// of `prefix` that would give the same result, i.e. not the
+/// `MultipleResults` error variant (again, using only the data of the
+/// `NodeTree`).
 fn lookup<'p>(
 ,
 prefix: NodePrefixRef<'p>,
-) -> Result, NodeMapError> {
-for visit_item in self.visit(prefix) {
+) -> Result<(Option, usize), NodeMapError> {
+for (i, visit_item) in self.visit(prefix).enumerate() {
 if let Some(opt) = visit_item.final_revision() {
-return Ok(opt);
+return Ok((opt, i + 1));
 }
 }
 Err(NodeMapError::MultipleResults)
@@ -607,6 +661,16 @@
 prefix: NodePrefixRef<'a>,
 ) -> Result, NodeMapError> {
 validate_candidate(idx, prefix.clone(), self.lookup(prefix)?)
+.map(|(opt, _shortest)| opt)
+}
+
+fn unique_prefix_len_bin<'a>(
+,
+idx:  RevlogIndex,
+prefix: NodePrefixRef<'a>,
+) -> Result, 

D8098: rust-nodemap: a method for full invalidation

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute created this revision.
Herald added subscribers: mercurial-devel, kevincox.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This will be used for exceptional operations,
  such as a `__delitem__` on the `MixedIndex` with
  Rust nodemap.
  
  In principle, `NodeTree` should also be able to forget
  an entry in an efficient way, by accepting to insert
  `Element::None` instead of only `Element::Rev(r)`,
  but that seems really overkill at this point. We need
  to support exceptional operations such as `__delitem__`,
  only for completeness of the revlog index as seen from
  Python. The Python callers don't seem to even really
  need it, deciding to drop the nodemap inconditionnally at
  at higher level when calling `hg strip`. Also, `hg strip`
  is very costly for reasons that are unrelated to nodemap
  aspects.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D8098

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -544,11 +544,25 @@
 Ok(())
 }
 
+/// Make the whole `NodeTree` logically empty, without touching the
+/// immutable part.
+pub fn invalidate_all( self) -> () {
+self.root = Block::new();
+self.growable = Vec::new();
+self.masked_inner_blocks = self.readonly.len()
+}
+
 /// Return the number of blocks in the readonly part that are currently
 /// masked in the mutable part.
 ///
 /// The `NodeTree` structure has no efficient way to know how many blocks
 /// are already unreachable in the readonly part.
+///
+/// After a call to `invalidate_all()`, the returned number can be actually
+/// bigger than the whole readonly part, a conventional way to mean that
+/// all the readonly blocks have been masked. This is what is really
+/// useful to the caller and does not require to know how many were
+/// actually unreachable to begin with.
 pub fn masked_readonly_blocks() -> usize {
 if let Some(readonly_root) = self.readonly.last() {
 if readonly_root ==  {
@@ -1025,6 +1039,27 @@
 }
 
 #[test]
+fn test_invalidate_all() -> Result<(), NodeMapError> {
+let mut idx = TestNtIndex::new();
+idx.insert(0, "1234")?;
+idx.insert(1, "1235")?;
+idx.insert(2, "131")?;
+idx.insert(3, "cafe")?;
+let mut idx = idx.commit();
+
+idx.nt.invalidate_all();
+
+assert_eq!(idx.find_hex("1234")?, None);
+assert_eq!(idx.find_hex("1235")?, None);
+assert_eq!(idx.find_hex("131")?, None);
+assert_eq!(idx.find_hex("cafe")?, None);
+// all the readonly blocks have been masked, this is the
+// conventional expected response
+assert_eq!(idx.nt.masked_readonly_blocks(), idx.nt.readonly.len() + 1);
+Ok(())
+}
+
+#[test]
 fn test_into_added_empty() {
 assert!(sample_nodetree().into_readonly_and_added().1.is_empty());
 assert!(sample_nodetree()



To: marmoute, #hg-reviewers
Cc: kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7797: rust-nodemap: pure Rust example

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute updated this revision to Diff 20026.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7797?vs=19637=20026

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7797/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7797

AFFECTED FILES
  rust/Cargo.lock
  rust/hg-core/Cargo.toml
  rust/hg-core/examples/nodemap/index.rs
  rust/hg-core/examples/nodemap/main.rs

CHANGE DETAILS

diff --git a/rust/hg-core/examples/nodemap/main.rs 
b/rust/hg-core/examples/nodemap/main.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/examples/nodemap/main.rs
@@ -0,0 +1,150 @@
+// Copyright 2019-2020 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+extern crate clap;
+extern crate hg;
+extern crate memmap;
+
+use clap::*;
+use hg::revlog::node::*;
+use hg::revlog::nodemap::*;
+use hg::revlog::*;
+use memmap::MmapOptions;
+use rand::Rng;
+use std::fs::File;
+use std::io;
+use std::io::Write;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::time::Instant;
+
+mod index;
+use index::Index;
+
+fn mmap_index(repo_path: ) -> Index {
+let mut path = PathBuf::from(repo_path);
+path.extend([".hg", "store", "00changelog.i"].iter());
+Index::load_mmap(path)
+}
+
+fn mmap_nodemap(path: ) -> NodeTree {
+let file = File::open(path).unwrap();
+let mmap = unsafe { MmapOptions::new().map().unwrap() };
+let len = mmap.len();
+NodeTree::load_bytes(Box::new(mmap), len)
+}
+
+/// Scan the whole index and create the corresponding nodemap file at `path`
+fn create(index: , path: ) -> io::Result<()> {
+let mut file = File::create(path)?;
+let start = Instant::now();
+let mut nm = NodeTree::default();
+for rev in 0..index.len() {
+let rev = rev as Revision;
+nm.insert(index, index.node(rev).unwrap(), rev).unwrap();
+}
+eprintln!("Nodemap constructed in RAM in {:?}", start.elapsed());
+file.write(_readonly_and_added_bytes().1)?;
+eprintln!("Nodemap written to disk");
+Ok(())
+}
+
+fn query(index: , nm: , prefix: ) {
+let start = Instant::now();
+let res = nm.find_hex(index, prefix);
+println!("Result found in {:?}: {:?}", start.elapsed(), res);
+}
+
+fn bench(index: , nm: , queries: usize) {
+let len = index.len() as u32;
+let mut rng = rand::thread_rng();
+let nodes: Vec = (0..queries)
+.map(|_| {
+index
+.node((rng.gen::() % len) as Revision)
+.unwrap()
+.clone()
+})
+.collect();
+if queries < 10 {
+let nodes_hex: Vec =
+nodes.iter().map(|n| n.encode_hex()).collect();
+println!("Nodes: {:?}", nodes_hex);
+}
+let mut last: Option = None;
+let start = Instant::now();
+for node in nodes.iter() {
+last = nm.find_bin(index, node.into()).unwrap();
+}
+let elapsed = start.elapsed();
+println!(
+"Did {} queries in {:?} (mean {:?}), last was {:?} with result {:?}",
+queries,
+elapsed,
+elapsed / (queries as u32),
+nodes.last().unwrap().encode_hex(),
+last
+);
+}
+
+fn main() {
+let matches = App::new("Nodemap pure Rust example")
+.arg(
+Arg::with_name("REPOSITORY")
+.help("Path to the repository, always necessary for its index")
+.required(true),
+)
+.arg(
+Arg::with_name("NODEMAP_FILE")
+.help("Path to the nodemap file, independent of REPOSITORY")
+.required(true),
+)
+.subcommand(
+SubCommand::with_name("create")
+.about("Create NODEMAP_FILE by scanning repository index"),
+)
+.subcommand(
+SubCommand::with_name("query")
+.about("Query NODEMAP_FILE for PREFIX")
+.arg(Arg::with_name("PREFIX").required(true)),
+)
+.subcommand(
+SubCommand::with_name("bench")
+.about(
+"Perform #QUERIES random successful queries on 
NODEMAP_FILE")
+.arg(Arg::with_name("QUERIES").required(true)),
+)
+.get_matches();
+
+let repo = matches.value_of("REPOSITORY").unwrap();
+let nm_path = matches.value_of("NODEMAP_FILE").unwrap();
+
+let index = mmap_index(::new(repo));
+
+if let Some(_) = matches.subcommand_matches("create") {
+println!("Creating nodemap file {} for repository {}", nm_path, repo);
+create(, ::new(nm_path)).unwrap();
+return;
+}
+
+let nm = mmap_nodemap(::new(nm_path));
+if let Some(matches) = matches.subcommand_matches("query") {
+let prefix = matches.value_of("PREFIX").unwrap();
+println!(
+"Querying {} in nodemap file {} of repository {}",
+prefix, nm_path, repo
+   

D8097: rust-nodemap: accounting for dead blocks

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute created this revision.
Herald added subscribers: mercurial-devel, kevincox.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  By the very append-only nature of the `NodeTree`, inserting
  new blocks has the effect of making some of the older ones
  useless as they become unreachable.
  
  Therefore some automatic housekeeping will need to be provided.
  This is standard procedure in the word of databases, under names
  such as "repack" or "vacuum".
  
  The new `masked_readonly_blocks()` will provide callers with
  useful information to decide if the nodetree is ripe for
  repacking, but all the `NodeTree` can provide is how many
  blocks have been masked in the currently mutable part. Analysing
  the readonly part would be way too long to do it for each
  transaction and defeat the whole purpose of nodemap persistence.
  Serializing callers (from the Python layer) will get this figure
  before each extraction and maintain an aggregate counter of
  unreachable blocks separately.
  
  Note: at this point, the most efficient repacking is just to restart
  afresh with a full rescan.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D8097

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -254,6 +254,7 @@
 readonly: Box + Send>,
 growable: Vec,
 root: Block,
+masked_inner_blocks: usize,
 }
 
 impl Index for NodeTree {
@@ -330,6 +331,7 @@
 readonly: readonly,
 growable: Vec::new(),
 root: root,
+masked_inner_blocks: 0,
 }
 }
 
@@ -452,6 +454,7 @@
 let ro_len = ro_blocks.len();
 let glen = self.growable.len();
 if idx < ro_len {
+self.masked_inner_blocks += 1;
 // TODO OPTIM I think this makes two copies
 self.growable.push(ro_blocks[idx].clone());
 (glen + ro_len,  self.growable[glen], glen + 1)
@@ -540,6 +543,22 @@
 }
 Ok(())
 }
+
+/// Return the number of blocks in the readonly part that are currently
+/// masked in the mutable part.
+///
+/// The `NodeTree` structure has no efficient way to know how many blocks
+/// are already unreachable in the readonly part.
+pub fn masked_readonly_blocks() -> usize {
+if let Some(readonly_root) = self.readonly.last() {
+if readonly_root ==  {
+return 0;
+}
+} else {
+return 0;
+}
+self.masked_inner_blocks + 1
+}
 }
 
 pub struct NodeTreeBytes {
@@ -818,6 +837,7 @@
 readonly: sample_nodetree().readonly,
 growable: vec![block![0: Rev(1), 5: Rev(3)]],
 root: block![0: Block(1), 1:Block(3), 12: Rev(2)],
+masked_inner_blocks: 1,
 };
 assert_eq!(nt.find_hex(, "10")?, Some(1));
 assert_eq!(nt.find_hex(, "c")?, Some(2));
@@ -826,6 +846,7 @@
 assert_eq!(nt.find_hex(, "000")?, Some(NULL_REVISION));
 assert_eq!(nt.unique_prefix_len_hex(, "000")?, Some(3));
 assert_eq!(nt.find_hex(, "01")?, Some(9));
+assert_eq!(nt.masked_readonly_blocks(), 2);
 Ok(())
 }
 
@@ -915,6 +936,8 @@
 assert_eq!(idx.find_hex("1a345")?, Some(3));
 assert_eq!(idx.find_hex("1a341")?, None);
 
+// there's no readonly block to mask
+assert_eq!(idx.nt.masked_readonly_blocks(), 0);
 Ok(())
 }
 
@@ -976,6 +999,8 @@
 assert_eq!(idx.find_hex("1235")?, Some(1));
 assert_eq!(idx.find_hex("131")?, Some(2));
 assert_eq!(idx.find_hex("cafe")?, Some(3));
+// we did not add anything since init from readonly
+assert_eq!(idx.nt.masked_readonly_blocks(), 0);
 
 idx.insert(4, "123A")?;
 assert_eq!(idx.find_hex("1234")?, Some(0));
@@ -983,12 +1008,18 @@
 assert_eq!(idx.find_hex("131")?, Some(2));
 assert_eq!(idx.find_hex("cafe")?, Some(3));
 assert_eq!(idx.find_hex("123A")?, Some(4));
+// we masked blocks for all prefixes of "123", including the root
+assert_eq!(idx.nt.masked_readonly_blocks(), 4);
 
+eprintln!("{:?}", idx.nt);
 idx.insert(5, "c0")?;
 assert_eq!(idx.find_hex("cafe")?, Some(3));
 assert_eq!(idx.find_hex("c0")?, Some(5));
 assert_eq!(idx.find_hex("c1")?, None);
 assert_eq!(idx.find_hex("1234")?, Some(0));
+// inserting "c0" is just splitting the 'c' slot of the mutable root,
+// it doesn't mask anything
+assert_eq!(idx.nt.masked_readonly_blocks(), 4);
 
 Ok(())
 }



To: marmoute, #hg-reviewers
Cc: kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org

D7798: rust-nodemap: special case for prefixes of NULL_NODE

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute updated this revision to Diff 20027.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7798?vs=19638=20027

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7798/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7798

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -13,7 +13,8 @@
 //! is used in a more abstract context.
 
 use super::{
-Node, NodeError, NodePrefix, NodePrefixRef, Revision, RevlogIndex,
+node::NULL_NODE, Node, NodeError, NodePrefix, NodePrefixRef, Revision,
+RevlogIndex, NULL_REVISION,
 };
 
 use std::fmt;
@@ -250,6 +251,31 @@
 })
 }
 
+/// validate that the candidate's node starts indeed with given prefix,
+/// and treat ambiguities related to `NULL_REVISION`.
+///
+/// From the data in the NodeTree, one can only conclude that some
+/// revision is the only one for a *subprefix* of the one being looked up.
+fn validate_candidate<'p>(
+idx:  RevlogIndex,
+prefix: NodePrefixRef<'p>,
+rev: Option,
+) -> Result, NodeMapError> {
+if prefix.is_prefix_of(_NODE) {
+// NULL_REVISION always matches a prefix made only of zeros
+// and any other *valid* result is an ambiguity
+match rev {
+None => Ok(Some(NULL_REVISION)),
+Some(r) => match has_prefix_or_none(idx, prefix, r)? {
+None => Ok(Some(NULL_REVISION)),
+_ => Err(NodeMapError::MultipleResults),
+},
+}
+} else {
+rev.map_or(Ok(None), |r| has_prefix_or_none(idx, prefix, r))
+}
+}
+
 impl NodeTree {
 /// Initiate a NodeTree from an immutable slice-like of `Block`
 ///
@@ -330,8 +356,6 @@
 }
 
 /// Main working method for `NodeTree` searches
-///
-/// This partial implementation lacks special cases for NULL_REVISION
 fn lookup<'p>(
 ,
 prefix: NodePrefixRef<'p>,
@@ -582,9 +606,7 @@
 idx:  RevlogIndex,
 prefix: NodePrefixRef<'a>,
 ) -> Result, NodeMapError> {
-self.lookup(prefix.clone()).and_then(|opt| {
-opt.map_or(Ok(None), |rev| has_prefix_or_none(idx, prefix, rev))
-})
+validate_candidate(idx, prefix.clone(), self.lookup(prefix)?)
 }
 }
 
@@ -713,8 +735,9 @@
 
 assert_eq!(nt.find_hex(, "0"), Err(MultipleResults));
 assert_eq!(nt.find_hex(, "01"), Ok(Some(9)));
-assert_eq!(nt.find_hex(, "00"), Ok(Some(0)));
+assert_eq!(nt.find_hex(, "00"), Err(MultipleResults));
 assert_eq!(nt.find_hex(, "00a"), Ok(Some(0)));
+assert_eq!(nt.find_hex(, "000"), Ok(Some(NULL_REVISION)));
 }
 
 #[test]
@@ -733,7 +756,8 @@
 };
 assert_eq!(nt.find_hex(, "10")?, Some(1));
 assert_eq!(nt.find_hex(, "c")?, Some(2));
-assert_eq!(nt.find_hex(, "00")?, Some(0));
+assert_eq!(nt.find_hex(, "00"), Err(MultipleResults));
+assert_eq!(nt.find_hex(, "000")?, Some(NULL_REVISION));
 assert_eq!(nt.find_hex(, "01")?, Some(9));
 Ok(())
 }



To: gracinet, #hg-reviewers, kevincox, marmoute
Cc: marmoute, durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7796: rust-nodemap: input/output primitives

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute updated this revision to Diff 20025.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7796?vs=19636=20025

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7796/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7796

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -17,8 +17,10 @@
 };
 
 use std::fmt;
+use std::mem;
 use std::ops::Deref;
 use std::ops::Index;
+use std::slice;
 
 #[derive(Debug, PartialEq)]
 pub enum NodeMapError {
@@ -175,6 +177,8 @@
 #[derive(Clone, PartialEq)]
 pub struct Block([RawElement; 16]);
 
+pub const BLOCK_SIZE: usize = mem::size_of::();
+
 impl Block {
 fn new() -> Self {
 Block([-1; 16])
@@ -262,6 +266,56 @@
 }
 }
 
+/// Create from an opaque bunch of bytes
+///
+/// The created `NodeTreeBytes` from `buffer`,
+/// of which exactly `amount` bytes are used.
+///
+/// - `buffer` could be derived from `PyBuffer` and `Mmap` objects.
+/// - `offset` allows for the final file format to include fixed data
+///   (generation number, behavioural flags)
+/// - `amount` is expressed in bytes, and is not automatically derived from
+///   `bytes`, so that a caller that manages them atomically can perform
+///   temporary disk serializations and still rollback easily if needed.
+///   First use-case for this would be to support Mercurial shell hooks.
+///
+/// panics if `buffer` is smaller than `amount`
+pub fn load_bytes(
+bytes: Box + Send>,
+amount: usize,
+) -> Self {
+NodeTree::new(Box::new(NodeTreeBytes::new(bytes, amount)))
+}
+
+/// Retrieve added `Block` and the original immutable data
+pub fn into_readonly_and_added(
+self,
+) -> (Box + Send>, Vec) {
+let mut vec = self.growable;
+let readonly = self.readonly;
+if readonly.last() != Some() {
+vec.push(self.root);
+}
+(readonly, vec)
+}
+
+/// Retrieve added `Blocks` as bytes, ready to be written to persistent
+/// storage
+pub fn into_readonly_and_added_bytes(
+self,
+) -> (Box + Send>, Vec) {
+let (readonly, vec) = self.into_readonly_and_added();
+let bytes = unsafe {
+Vec::from_raw_parts(
+vec.as_ptr() as *mut u8,
+vec.len() * BLOCK_SIZE,
+vec.capacity() * BLOCK_SIZE,
+)
+};
+mem::forget(vec);
+(readonly, bytes)
+}
+
 /// Total number of blocks
 fn len() -> usize {
 self.readonly.len() + self.growable.len() + 1
@@ -410,6 +464,38 @@
 }
 }
 
+pub struct NodeTreeBytes {
+buffer: Box + Send>,
+len_in_blocks: usize,
+}
+
+impl NodeTreeBytes {
+fn new(
+buffer: Box + Send>,
+amount: usize,
+) -> Self {
+assert!(buffer.len() >= amount);
+let len_in_blocks = amount / BLOCK_SIZE;
+NodeTreeBytes {
+buffer,
+len_in_blocks,
+}
+}
+}
+
+impl Deref for NodeTreeBytes {
+type Target = [Block];
+
+fn deref() -> &[Block] {
+unsafe {
+slice::from_raw_parts(
+().as_ptr() as *const Block,
+self.len_in_blocks,
+)
+}
+}
+}
+
 struct NodeTreeVisitor<'n, 'p> {
 nt: &'n NodeTree,
 prefix: NodePrefixRef<'p>,
@@ -786,4 +872,30 @@
 
 Ok(())
 }
+
+#[test]
+fn test_into_added_empty() {
+assert!(sample_nodetree().into_readonly_and_added().1.is_empty());
+assert!(sample_nodetree()
+.into_readonly_and_added_bytes()
+.1
+.is_empty());
+}
+
+#[test]
+fn test_into_added_bytes() -> Result<(), NodeMapError> {
+let mut idx = TestNtIndex::new();
+idx.insert(0, "1234")?;
+let mut idx = idx.commit();
+idx.insert(4, "cafe")?;
+let (_, bytes) = idx.nt.into_readonly_and_added_bytes();
+
+// only the root block has been changed
+assert_eq!(bytes.len(), BLOCK_SIZE);
+// big endian for -2
+assert_eq!([4..2 * 4], [255, 255, 255, 254]);
+// big endian for -6
+assert_eq!([12 * 4..13 * 4], [255, 255, 255, 250]);
+Ok(())
+}
 }



To: gracinet, #hg-reviewers, kevincox
Cc: marmoute, durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7795: rust-nodemap: insert method

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute updated this revision to Diff 20024.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7795?vs=19635=20024

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7795/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7795

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -15,6 +15,7 @@
 use super::{
 Node, NodeError, NodePrefix, NodePrefixRef, Revision, RevlogIndex,
 };
+
 use std::fmt;
 use std::ops::Deref;
 use std::ops::Index;
@@ -96,6 +97,15 @@
 }
 }
 
+pub trait MutableNodeMap: NodeMap {
+fn insert(
+ self,
+index: ,
+node: ,
+rev: Revision,
+) -> Result<(), NodeMapError>;
+}
+
 /// Low level NodeTree [`Blocks`] elements
 ///
 /// These are exactly as for instance on persistent storage.
@@ -292,6 +302,112 @@
 done: false,
 }
 }
+/// Return a mutable reference for `Block` at index `idx`.
+///
+/// If `idx` lies in the immutable area, then the reference is to
+/// a newly appended copy.
+///
+/// Returns (new_idx, glen, mut_ref) where
+///
+/// - `new_idx` is the index of the mutable `Block`
+/// - `mut_ref` is a mutable reference to the mutable Block.
+/// - `glen` is the new length of `self.growable`
+///
+/// Note: the caller wouldn't be allowed to query `self.growable.len()`
+/// itself because of the mutable borrow taken with the returned `Block`
+fn mutable_block( self, idx: usize) -> (usize,  Block, usize) {
+let ro_blocks = 
+let ro_len = ro_blocks.len();
+let glen = self.growable.len();
+if idx < ro_len {
+// TODO OPTIM I think this makes two copies
+self.growable.push(ro_blocks[idx].clone());
+(glen + ro_len,  self.growable[glen], glen + 1)
+} else if glen + ro_len == idx {
+(idx,  self.root, glen)
+} else {
+(idx,  self.growable[idx - ro_len], glen)
+}
+}
+
+/// Main insertion method
+///
+/// This will dive in the node tree to find the deepest `Block` for
+/// `node`, split it as much as needed and record `node` in there.
+/// The method then backtracks, updating references in all the visited
+/// blocks from the root.
+///
+/// All the mutated `Block` are copied first to the growable part if
+/// needed. That happens for those in the immutable part except the root.
+pub fn insert(
+ self,
+index: ,
+node: ,
+rev: Revision,
+) -> Result<(), NodeMapError> {
+let ro_len = ();
+
+let mut visit_steps: Vec<_> = self.visit(node.into()).collect();
+let read_nybbles = visit_steps.len();
+// visit_steps cannot be empty, since we always visit the root block
+let deepest = visit_steps.pop().unwrap();
+
+let (mut block_idx, mut block, mut glen) =
+self.mutable_block(deepest.block_idx);
+
+if let Element::Rev(old_rev) = deepest.element {
+let old_node = index
+.node(old_rev)
+.ok_or_else(|| NodeMapError::RevisionNotInIndex(old_rev))?;
+if old_node == node {
+return Ok(()); // avoid creating lots of useless blocks
+}
+
+// Looping over the tail of nybbles in both nodes, creating
+// new blocks until we find the difference
+let mut new_block_idx = ro_len + glen;
+let mut nybble = deepest.nybble;
+for nybble_pos in read_nybbles..node.nybbles_len() {
+block.set(nybble, Element::Block(new_block_idx));
+
+let new_nybble = node.get_nybble(nybble_pos);
+let old_nybble = old_node.get_nybble(nybble_pos);
+
+if old_nybble == new_nybble {
+self.growable.push(Block::new());
+block =  self.growable[glen];
+glen += 1;
+new_block_idx += 1;
+nybble = new_nybble;
+} else {
+let mut new_block = Block::new();
+new_block.set(old_nybble, Element::Rev(old_rev));
+new_block.set(new_nybble, Element::Rev(rev));
+self.growable.push(new_block);
+break;
+}
+}
+} else {
+// Free slot in the deepest block: no splitting has to be done
+block.set(deepest.nybble, Element::Rev(rev));
+}
+
+// Backtrack over visit steps to update references
+while let Some(visited) = visit_steps.pop() {
+let to_write = Element::Block(block_idx);
+if visit_steps.is_empty() {
+

D8096: purge: add -i flag to delete ignored files instead of untracked files

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute added a comment.
marmoute accepted this revision.


  The feature is great (I actually needs it from time to time) and the code 
looks good.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D8096/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D8096

To: valentin.gatienbaron, #hg-reviewers, marmoute
Cc: marmoute, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D8096: purge: add -i flag to delete ignored files instead of untracked files

2020-02-08 Thread valentin.gatienbaron (Valentin Gatien-Baron)
valentin.gatienbaron created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  It's convenient for deleting build artifacts. Using --all instead
  would delete other things too.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D8096

AFFECTED FILES
  hgext/purge.py
  mercurial/merge.py
  tests/test-purge.t

CHANGE DETAILS

diff --git a/tests/test-purge.t b/tests/test-purge.t
--- a/tests/test-purge.t
+++ b/tests/test-purge.t
@@ -120,19 +120,32 @@
   directory/untracked_file
   $ rm directory/untracked_file
 
-skip ignored files if --all not specified
+skip ignored files if -i or --all not specified
 
   $ touch ignored
   $ hg purge -p
   $ hg purge -v
+  $ touch untracked_file
   $ ls
   directory
   ignored
   r1
+  untracked_file
+  $ hg purge -p -i
+  ignored
+  $ hg purge -v -i
+  removing file ignored
+  $ ls
+  directory
+  r1
+  untracked_file
+  $ touch ignored
   $ hg purge -p --all
   ignored
+  untracked_file
   $ hg purge -v --all
   removing file ignored
+  removing file untracked_file
   $ ls
   directory
   r1
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -2698,6 +2698,7 @@
 def purge(
 repo,
 matcher,
+unknown=True,
 ignored=False,
 removeemptydirs=True,
 removefiles=True,
@@ -2709,7 +2710,9 @@
 ``matcher`` is a matcher configured to scan the working directory -
 potentially a subset.
 
-``ignored`` controls whether ignored files should also be purged.
+``unknown`` controls whether unknown files should be purged.
+
+``ignored`` controls whether ignored files should be purged.
 
 ``removeemptydirs`` controls whether empty directories should be removed.
 
@@ -2746,7 +2749,7 @@
 directories = []
 matcher.traversedir = directories.append
 
-status = repo.status(match=matcher, ignored=ignored, unknown=True)
+status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
 
 if removefiles:
 for f in sorted(status.unknown + status.ignored):
diff --git a/hgext/purge.py b/hgext/purge.py
--- a/hgext/purge.py
+++ b/hgext/purge.py
@@ -48,6 +48,7 @@
 [
 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
 (b'', b'all', None, _(b'purge ignored files too')),
+(b'i', b'ignored', None, _(b'purge only ignored files')),
 (b'', b'dirs', None, _(b'purge empty directories')),
 (b'', b'files', None, _(b'purge files')),
 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
@@ -80,7 +81,7 @@
 But it will leave untouched:
 
 - Modified and unmodified tracked files
-- Ignored files (unless --all is specified)
+- Ignored files (unless -i or --all is specified)
 - New files added to the repository (with :hg:`add`)
 
 The --files and --dirs options can be used to direct purge to delete
@@ -102,6 +103,13 @@
 if opts.get(b'print0'):
 eol = b'\0'
 act = False  # --print0 implies --print
+if opts.get(b'all', False):
+ignored = True
+unknown = True
+else:
+ignored = opts.get(b'ignored', False)
+unknown = not ignored
+cmdutil.check_incompatible_arguments(opts, b'all', b'ignored')
 
 removefiles = opts.get(b'files')
 removedirs = opts.get(b'dirs')
@@ -115,7 +123,8 @@
 paths = mergemod.purge(
 repo,
 match,
-ignored=opts.get(b'all', False),
+unknown=unknown,
+ignored=ignored,
 removeemptydirs=removedirs,
 removefiles=removefiles,
 abortonerror=opts.get(b'abort_on_err'),



To: valentin.gatienbaron, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7961: lfs: add a method to the local blobstore to convert OIDs to file paths

2020-02-08 Thread marmoute (Pierre-Yves David)
marmoute added a comment.


  This change seems to break on python3:
  
  https://foss.heptapod.net/octobus/mercurial-devel/-/jobs/56838

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7961/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7961

To: mharbison72, #hg-reviewers, marmoute
Cc: marmoute, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel