|
@@ -53,10 +53,10 @@ if TYPE_CHECKING:
|
|
|
from .config import ConditionMatcher, ConfigFile, StackedConfig
|
|
|
from .index import Index
|
|
|
from .notes import Notes
|
|
|
+ from .worktree import WorkTree
|
|
|
|
|
|
+from . import replace_me
|
|
|
from .errors import (
|
|
|
- CommitError,
|
|
|
- HookError,
|
|
|
NoIndexPresent,
|
|
|
NotBlobError,
|
|
|
NotCommitError,
|
|
@@ -987,6 +987,20 @@ class BaseRepo:
|
|
|
with f:
|
|
|
return [line.strip() for line in f.readlines() if line.strip()]
|
|
|
|
|
|
+ def get_worktree(self) -> "WorkTree":
|
|
|
+ """Get the working tree for this repository.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ WorkTree instance for performing working tree operations
|
|
|
+
|
|
|
+ Raises:
|
|
|
+ NotImplementedError: If the repository doesn't support working trees
|
|
|
+ """
|
|
|
+ raise NotImplementedError(
|
|
|
+ "Working tree operations not supported by this repository type"
|
|
|
+ )
|
|
|
+
|
|
|
+ @replace_me()
|
|
|
def do_commit(
|
|
|
self,
|
|
|
message: Optional[bytes] = None,
|
|
@@ -1034,158 +1048,21 @@ class BaseRepo:
|
|
|
Returns:
|
|
|
New commit SHA1
|
|
|
"""
|
|
|
- try:
|
|
|
- if not no_verify:
|
|
|
- self.hooks["pre-commit"].execute()
|
|
|
- except HookError as exc:
|
|
|
- raise CommitError(exc) from exc
|
|
|
- except KeyError: # no hook defined, silent fallthrough
|
|
|
- pass
|
|
|
-
|
|
|
- c = Commit()
|
|
|
- if tree is None:
|
|
|
- index = self.open_index()
|
|
|
- c.tree = index.commit(self.object_store)
|
|
|
- else:
|
|
|
- if len(tree) != 40:
|
|
|
- raise ValueError("tree must be a 40-byte hex sha string")
|
|
|
- c.tree = tree
|
|
|
-
|
|
|
- config = self.get_config_stack()
|
|
|
- if merge_heads is None:
|
|
|
- merge_heads = self._read_heads("MERGE_HEAD")
|
|
|
- if committer is None:
|
|
|
- committer = get_user_identity(config, kind="COMMITTER")
|
|
|
- check_user_identity(committer)
|
|
|
- c.committer = committer
|
|
|
- if commit_timestamp is None:
|
|
|
- # FIXME: Support GIT_COMMITTER_DATE environment variable
|
|
|
- commit_timestamp = time.time()
|
|
|
- c.commit_time = int(commit_timestamp)
|
|
|
- if commit_timezone is None:
|
|
|
- # FIXME: Use current user timezone rather than UTC
|
|
|
- commit_timezone = 0
|
|
|
- c.commit_timezone = commit_timezone
|
|
|
- if author is None:
|
|
|
- author = get_user_identity(config, kind="AUTHOR")
|
|
|
- c.author = author
|
|
|
- check_user_identity(author)
|
|
|
- if author_timestamp is None:
|
|
|
- # FIXME: Support GIT_AUTHOR_DATE environment variable
|
|
|
- author_timestamp = commit_timestamp
|
|
|
- c.author_time = int(author_timestamp)
|
|
|
- if author_timezone is None:
|
|
|
- author_timezone = commit_timezone
|
|
|
- c.author_timezone = author_timezone
|
|
|
- if encoding is None:
|
|
|
- try:
|
|
|
- encoding = config.get(("i18n",), "commitEncoding")
|
|
|
- except KeyError:
|
|
|
- pass # No dice
|
|
|
- if encoding is not None:
|
|
|
- c.encoding = encoding
|
|
|
- # Store original message (might be callable)
|
|
|
- original_message = message
|
|
|
- message = None # Will be set later after parents are set
|
|
|
-
|
|
|
- # Check if we should sign the commit
|
|
|
- should_sign = sign
|
|
|
- if sign is None:
|
|
|
- # Check commit.gpgSign configuration when sign is not explicitly set
|
|
|
- config = self.get_config_stack()
|
|
|
- try:
|
|
|
- should_sign = config.get_boolean((b"commit",), b"gpgSign")
|
|
|
- except KeyError:
|
|
|
- should_sign = False # Default to not signing if no config
|
|
|
- keyid = sign if isinstance(sign, str) else None
|
|
|
-
|
|
|
- if ref is None:
|
|
|
- # Create a dangling commit
|
|
|
- c.parents = merge_heads
|
|
|
- else:
|
|
|
- try:
|
|
|
- old_head = self.refs[ref]
|
|
|
- c.parents = [old_head, *merge_heads]
|
|
|
- except KeyError:
|
|
|
- c.parents = merge_heads
|
|
|
-
|
|
|
- # Handle message after parents are set
|
|
|
- if callable(original_message):
|
|
|
- message = original_message(self, c)
|
|
|
- if message is None:
|
|
|
- raise ValueError("Message callback returned None")
|
|
|
- else:
|
|
|
- message = original_message
|
|
|
-
|
|
|
- if message is None:
|
|
|
- # FIXME: Try to read commit message from .git/MERGE_MSG
|
|
|
- raise ValueError("No commit message specified")
|
|
|
-
|
|
|
- try:
|
|
|
- if no_verify:
|
|
|
- c.message = message
|
|
|
- else:
|
|
|
- c.message = self.hooks["commit-msg"].execute(message)
|
|
|
- if c.message is None:
|
|
|
- c.message = message
|
|
|
- except HookError as exc:
|
|
|
- raise CommitError(exc) from exc
|
|
|
- except KeyError: # no hook defined, message not modified
|
|
|
- c.message = message
|
|
|
-
|
|
|
- if ref is None:
|
|
|
- # Create a dangling commit
|
|
|
- if should_sign:
|
|
|
- c.sign(keyid)
|
|
|
- self.object_store.add_object(c)
|
|
|
- else:
|
|
|
- try:
|
|
|
- old_head = self.refs[ref]
|
|
|
- if should_sign:
|
|
|
- c.sign(keyid)
|
|
|
- self.object_store.add_object(c)
|
|
|
- ok = self.refs.set_if_equals(
|
|
|
- ref,
|
|
|
- old_head,
|
|
|
- c.id,
|
|
|
- message=b"commit: " + message,
|
|
|
- committer=committer,
|
|
|
- timestamp=commit_timestamp,
|
|
|
- timezone=commit_timezone,
|
|
|
- )
|
|
|
- except KeyError:
|
|
|
- c.parents = merge_heads
|
|
|
- if should_sign:
|
|
|
- c.sign(keyid)
|
|
|
- self.object_store.add_object(c)
|
|
|
- ok = self.refs.add_if_new(
|
|
|
- ref,
|
|
|
- c.id,
|
|
|
- message=b"commit: " + message,
|
|
|
- committer=committer,
|
|
|
- timestamp=commit_timestamp,
|
|
|
- timezone=commit_timezone,
|
|
|
- )
|
|
|
- if not ok:
|
|
|
- # Fail if the atomic compare-and-swap failed, leaving the
|
|
|
- # commit and all its objects as garbage.
|
|
|
- raise CommitError(f"{ref!r} changed during commit")
|
|
|
-
|
|
|
- self._del_named_file("MERGE_HEAD")
|
|
|
-
|
|
|
- try:
|
|
|
- self.hooks["post-commit"].execute()
|
|
|
- except HookError as e: # silent failure
|
|
|
- warnings.warn(f"post-commit hook failed: {e}", UserWarning)
|
|
|
- except KeyError: # no hook defined, silent fallthrough
|
|
|
- pass
|
|
|
-
|
|
|
- # Trigger auto GC if needed
|
|
|
- from .gc import maybe_auto_gc
|
|
|
-
|
|
|
- maybe_auto_gc(self)
|
|
|
-
|
|
|
- return c.id
|
|
|
+ return self.get_worktree().commit(
|
|
|
+ message=message,
|
|
|
+ committer=committer,
|
|
|
+ author=author,
|
|
|
+ commit_timestamp=commit_timestamp,
|
|
|
+ commit_timezone=commit_timezone,
|
|
|
+ author_timestamp=author_timestamp,
|
|
|
+ author_timezone=author_timezone,
|
|
|
+ tree=tree,
|
|
|
+ encoding=encoding,
|
|
|
+ ref=ref,
|
|
|
+ merge_heads=merge_heads,
|
|
|
+ no_verify=no_verify,
|
|
|
+ sign=sign,
|
|
|
+ )
|
|
|
|
|
|
|
|
|
def read_gitfile(f):
|
|
@@ -1350,6 +1227,16 @@ class Repo(BaseRepo):
|
|
|
self.hooks["post-commit"] = PostCommitShellHook(self.controldir())
|
|
|
self.hooks["post-receive"] = PostReceiveShellHook(self.controldir())
|
|
|
|
|
|
+ def get_worktree(self) -> "WorkTree":
|
|
|
+ """Get the working tree for this repository.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ WorkTree instance for performing working tree operations
|
|
|
+ """
|
|
|
+ from .worktree import WorkTree
|
|
|
+
|
|
|
+ return WorkTree(self, self.path)
|
|
|
+
|
|
|
def _write_reflog(
|
|
|
self, ref, old_sha, new_sha, committer, timestamp, timezone, message
|
|
|
) -> None:
|
|
@@ -1547,6 +1434,7 @@ class Repo(BaseRepo):
|
|
|
# missing index file, which is treated as empty.
|
|
|
return not self.bare
|
|
|
|
|
|
+ @replace_me()
|
|
|
def stage(
|
|
|
self,
|
|
|
fs_paths: Union[
|
|
@@ -1558,117 +1446,16 @@ class Repo(BaseRepo):
|
|
|
Args:
|
|
|
fs_paths: List of paths, relative to the repository path
|
|
|
"""
|
|
|
- root_path_bytes = os.fsencode(self.path)
|
|
|
-
|
|
|
- if isinstance(fs_paths, (str, bytes, os.PathLike)):
|
|
|
- fs_paths = [fs_paths]
|
|
|
- fs_paths = list(fs_paths)
|
|
|
-
|
|
|
- from .index import (
|
|
|
- _fs_to_tree_path,
|
|
|
- blob_from_path_and_stat,
|
|
|
- index_entry_from_directory,
|
|
|
- index_entry_from_stat,
|
|
|
- )
|
|
|
-
|
|
|
- index = self.open_index()
|
|
|
- blob_normalizer = self.get_blob_normalizer()
|
|
|
- for fs_path in fs_paths:
|
|
|
- if not isinstance(fs_path, bytes):
|
|
|
- fs_path = os.fsencode(fs_path)
|
|
|
- if os.path.isabs(fs_path):
|
|
|
- raise ValueError(
|
|
|
- f"path {fs_path!r} should be relative to "
|
|
|
- "repository root, not absolute"
|
|
|
- )
|
|
|
- tree_path = _fs_to_tree_path(fs_path)
|
|
|
- full_path = os.path.join(root_path_bytes, fs_path)
|
|
|
- try:
|
|
|
- st = os.lstat(full_path)
|
|
|
- except OSError:
|
|
|
- # File no longer exists
|
|
|
- try:
|
|
|
- del index[tree_path]
|
|
|
- except KeyError:
|
|
|
- pass # already removed
|
|
|
- else:
|
|
|
- if stat.S_ISDIR(st.st_mode):
|
|
|
- entry = index_entry_from_directory(st, full_path)
|
|
|
- if entry:
|
|
|
- index[tree_path] = entry
|
|
|
- else:
|
|
|
- try:
|
|
|
- del index[tree_path]
|
|
|
- except KeyError:
|
|
|
- pass
|
|
|
- elif not stat.S_ISREG(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
|
- try:
|
|
|
- del index[tree_path]
|
|
|
- except KeyError:
|
|
|
- pass
|
|
|
- else:
|
|
|
- blob = blob_from_path_and_stat(full_path, st)
|
|
|
- blob = blob_normalizer.checkin_normalize(blob, fs_path)
|
|
|
- self.object_store.add_object(blob)
|
|
|
- index[tree_path] = index_entry_from_stat(st, blob.id)
|
|
|
- index.write()
|
|
|
+ return self.get_worktree().stage(fs_paths)
|
|
|
|
|
|
+ @replace_me()
|
|
|
def unstage(self, fs_paths: list[str]) -> None:
|
|
|
"""Unstage specific file in the index
|
|
|
Args:
|
|
|
fs_paths: a list of files to unstage,
|
|
|
relative to the repository path.
|
|
|
"""
|
|
|
- from .index import IndexEntry, _fs_to_tree_path
|
|
|
-
|
|
|
- index = self.open_index()
|
|
|
- try:
|
|
|
- tree_id = self[b"HEAD"].tree
|
|
|
- except KeyError:
|
|
|
- # no head mean no commit in the repo
|
|
|
- for fs_path in fs_paths:
|
|
|
- tree_path = _fs_to_tree_path(fs_path)
|
|
|
- del index[tree_path]
|
|
|
- index.write()
|
|
|
- return
|
|
|
-
|
|
|
- for fs_path in fs_paths:
|
|
|
- tree_path = _fs_to_tree_path(fs_path)
|
|
|
- try:
|
|
|
- tree = self.object_store[tree_id]
|
|
|
- assert isinstance(tree, Tree)
|
|
|
- tree_entry = tree.lookup_path(self.object_store.__getitem__, tree_path)
|
|
|
- except KeyError:
|
|
|
- # if tree_entry didn't exist, this file was being added, so
|
|
|
- # remove index entry
|
|
|
- try:
|
|
|
- del index[tree_path]
|
|
|
- continue
|
|
|
- except KeyError as exc:
|
|
|
- raise KeyError(f"file '{tree_path.decode()}' not in index") from exc
|
|
|
-
|
|
|
- st = None
|
|
|
- try:
|
|
|
- st = os.lstat(os.path.join(self.path, fs_path))
|
|
|
- except FileNotFoundError:
|
|
|
- pass
|
|
|
-
|
|
|
- index_entry = IndexEntry(
|
|
|
- ctime=(self[b"HEAD"].commit_time, 0),
|
|
|
- mtime=(self[b"HEAD"].commit_time, 0),
|
|
|
- dev=st.st_dev if st else 0,
|
|
|
- ino=st.st_ino if st else 0,
|
|
|
- mode=tree_entry[0],
|
|
|
- uid=st.st_uid if st else 0,
|
|
|
- gid=st.st_gid if st else 0,
|
|
|
- size=len(self[tree_entry[1]].data),
|
|
|
- sha=tree_entry[1],
|
|
|
- flags=0,
|
|
|
- extended_flags=0,
|
|
|
- )
|
|
|
-
|
|
|
- index[tree_path] = index_entry
|
|
|
- index.write()
|
|
|
+ return self.get_worktree().unstage(fs_paths)
|
|
|
|
|
|
def clone(
|
|
|
self,
|
|
@@ -1765,55 +1552,14 @@ class Repo(BaseRepo):
|
|
|
raise
|
|
|
return target
|
|
|
|
|
|
+ @replace_me()
|
|
|
def reset_index(self, tree: Optional[bytes] = None):
|
|
|
"""Reset the index back to a specific tree.
|
|
|
|
|
|
Args:
|
|
|
tree: Tree SHA to reset to, None for current HEAD tree.
|
|
|
"""
|
|
|
- from .index import (
|
|
|
- build_index_from_tree,
|
|
|
- symlink,
|
|
|
- validate_path_element_default,
|
|
|
- validate_path_element_hfs,
|
|
|
- validate_path_element_ntfs,
|
|
|
- )
|
|
|
-
|
|
|
- if tree is None:
|
|
|
- head = self[b"HEAD"]
|
|
|
- if isinstance(head, Tag):
|
|
|
- _cls, obj = head.object
|
|
|
- head = self.get_object(obj)
|
|
|
- tree = head.tree
|
|
|
- config = self.get_config()
|
|
|
- honor_filemode = config.get_boolean(b"core", b"filemode", os.name != "nt")
|
|
|
- if config.get_boolean(b"core", b"core.protectNTFS", os.name == "nt"):
|
|
|
- validate_path_element = validate_path_element_ntfs
|
|
|
- elif config.get_boolean(b"core", b"core.protectHFS", sys.platform == "darwin"):
|
|
|
- validate_path_element = validate_path_element_hfs
|
|
|
- else:
|
|
|
- validate_path_element = validate_path_element_default
|
|
|
- if config.get_boolean(b"core", b"symlinks", True):
|
|
|
- symlink_fn = symlink
|
|
|
- else:
|
|
|
-
|
|
|
- def symlink_fn(source, target) -> None: # type: ignore
|
|
|
- with open(
|
|
|
- target, "w" + ("b" if isinstance(source, bytes) else "")
|
|
|
- ) as f:
|
|
|
- f.write(source)
|
|
|
-
|
|
|
- blob_normalizer = self.get_blob_normalizer()
|
|
|
- return build_index_from_tree(
|
|
|
- self.path,
|
|
|
- self.index_path(),
|
|
|
- self.object_store,
|
|
|
- tree,
|
|
|
- honor_filemode=honor_filemode,
|
|
|
- validate_path_element=validate_path_element,
|
|
|
- symlink_fn=symlink_fn,
|
|
|
- blob_normalizer=blob_normalizer,
|
|
|
- )
|
|
|
+ return self.get_worktree().reset_index(tree)
|
|
|
|
|
|
def _get_config_condition_matchers(self) -> dict[str, "ConditionMatcher"]:
|
|
|
"""Get condition matchers for includeIf conditions.
|
|
@@ -2234,40 +1980,31 @@ class Repo(BaseRepo):
|
|
|
|
|
|
return GitAttributes(patterns)
|
|
|
|
|
|
+ @replace_me()
|
|
|
def _sparse_checkout_file_path(self) -> str:
|
|
|
"""Return the path of the sparse-checkout file in this repo's control dir."""
|
|
|
- return os.path.join(self.controldir(), "info", "sparse-checkout")
|
|
|
+ return self.get_worktree()._sparse_checkout_file_path()
|
|
|
|
|
|
+ @replace_me()
|
|
|
def configure_for_cone_mode(self) -> None:
|
|
|
"""Ensure the repository is configured for cone-mode sparse-checkout."""
|
|
|
- config = self.get_config()
|
|
|
- config.set((b"core",), b"sparseCheckout", b"true")
|
|
|
- config.set((b"core",), b"sparseCheckoutCone", b"true")
|
|
|
- config.write_to_path()
|
|
|
+ return self.get_worktree().configure_for_cone_mode()
|
|
|
|
|
|
+ @replace_me()
|
|
|
def infer_cone_mode(self) -> bool:
|
|
|
"""Return True if 'core.sparseCheckoutCone' is set to 'true' in config, else False."""
|
|
|
- config = self.get_config()
|
|
|
- try:
|
|
|
- sc_cone = config.get((b"core",), b"sparseCheckoutCone")
|
|
|
- return sc_cone == b"true"
|
|
|
- except KeyError:
|
|
|
- # If core.sparseCheckoutCone is not set, default to False
|
|
|
- return False
|
|
|
+ return self.get_worktree().infer_cone_mode()
|
|
|
|
|
|
+ @replace_me()
|
|
|
def get_sparse_checkout_patterns(self) -> list[str]:
|
|
|
"""Return a list of sparse-checkout patterns from info/sparse-checkout.
|
|
|
|
|
|
Returns:
|
|
|
A list of patterns. Returns an empty list if the file is missing.
|
|
|
"""
|
|
|
- path = self._sparse_checkout_file_path()
|
|
|
- try:
|
|
|
- with open(path, encoding="utf-8") as f:
|
|
|
- return [line.strip() for line in f if line.strip()]
|
|
|
- except FileNotFoundError:
|
|
|
- return []
|
|
|
+ return self.get_worktree().get_sparse_checkout_patterns()
|
|
|
|
|
|
+ @replace_me()
|
|
|
def set_sparse_checkout_patterns(self, patterns: list[str]) -> None:
|
|
|
"""Write the given sparse-checkout patterns into info/sparse-checkout.
|
|
|
|
|
@@ -2276,14 +2013,9 @@ class Repo(BaseRepo):
|
|
|
Args:
|
|
|
patterns: A list of gitignore-style patterns to store.
|
|
|
"""
|
|
|
- info_dir = os.path.join(self.controldir(), "info")
|
|
|
- os.makedirs(info_dir, exist_ok=True)
|
|
|
-
|
|
|
- path = self._sparse_checkout_file_path()
|
|
|
- with open(path, "w", encoding="utf-8") as f:
|
|
|
- for pat in patterns:
|
|
|
- f.write(pat + "\n")
|
|
|
+ return self.get_worktree().set_sparse_checkout_patterns(patterns)
|
|
|
|
|
|
+ @replace_me()
|
|
|
def set_cone_mode_patterns(self, dirs: Union[list[str], None] = None) -> None:
|
|
|
"""Write the given cone-mode directory patterns into info/sparse-checkout.
|
|
|
|
|
@@ -2291,14 +2023,7 @@ class Repo(BaseRepo):
|
|
|
``!/*/`` 'exclude' that re-includes that directory and everything under it.
|
|
|
Never add the same line twice.
|
|
|
"""
|
|
|
- patterns = ["/*", "!/*/"]
|
|
|
- if dirs:
|
|
|
- for d in dirs:
|
|
|
- d = d.strip("/")
|
|
|
- line = f"/{d}/"
|
|
|
- if d and line not in patterns:
|
|
|
- patterns.append(line)
|
|
|
- self.set_sparse_checkout_patterns(patterns)
|
|
|
+ return self.get_worktree().set_cone_mode_patterns(dirs)
|
|
|
|
|
|
|
|
|
class MemoryRepo(BaseRepo):
|
|
@@ -2420,6 +2145,136 @@ class MemoryRepo(BaseRepo):
|
|
|
# Return empty GitAttributes
|
|
|
return GitAttributes([])
|
|
|
|
|
|
+ def do_commit(
|
|
|
+ self,
|
|
|
+ message: Optional[bytes] = None,
|
|
|
+ committer: Optional[bytes] = None,
|
|
|
+ author: Optional[bytes] = None,
|
|
|
+ commit_timestamp=None,
|
|
|
+ commit_timezone=None,
|
|
|
+ author_timestamp=None,
|
|
|
+ author_timezone=None,
|
|
|
+ tree: Optional[ObjectID] = None,
|
|
|
+ encoding: Optional[bytes] = None,
|
|
|
+ ref: Optional[Ref] = b"HEAD",
|
|
|
+ merge_heads: Optional[list[ObjectID]] = None,
|
|
|
+ no_verify: bool = False,
|
|
|
+ sign: bool = False,
|
|
|
+ ):
|
|
|
+ """Create a new commit.
|
|
|
+
|
|
|
+ This is a simplified implementation for in-memory repositories that
|
|
|
+ doesn't support worktree operations or hooks.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ message: Commit message
|
|
|
+ committer: Committer fullname
|
|
|
+ author: Author fullname
|
|
|
+ commit_timestamp: Commit timestamp (defaults to now)
|
|
|
+ commit_timezone: Commit timestamp timezone (defaults to GMT)
|
|
|
+ author_timestamp: Author timestamp (defaults to commit timestamp)
|
|
|
+ author_timezone: Author timestamp timezone (defaults to commit timezone)
|
|
|
+ tree: SHA1 of the tree root to use
|
|
|
+ encoding: Encoding
|
|
|
+ ref: Optional ref to commit to (defaults to current branch).
|
|
|
+ If None, creates a dangling commit without updating any ref.
|
|
|
+ merge_heads: Merge heads
|
|
|
+ no_verify: Skip pre-commit and commit-msg hooks (ignored for MemoryRepo)
|
|
|
+ sign: GPG Sign the commit (ignored for MemoryRepo)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ New commit SHA1
|
|
|
+ """
|
|
|
+ import time
|
|
|
+
|
|
|
+ from .objects import Commit
|
|
|
+
|
|
|
+ if tree is None:
|
|
|
+ raise ValueError("tree must be specified for MemoryRepo")
|
|
|
+
|
|
|
+ c = Commit()
|
|
|
+ if len(tree) != 40:
|
|
|
+ raise ValueError("tree must be a 40-byte hex sha string")
|
|
|
+ c.tree = tree
|
|
|
+
|
|
|
+ config = self.get_config_stack()
|
|
|
+ if merge_heads is None:
|
|
|
+ merge_heads = []
|
|
|
+ if committer is None:
|
|
|
+ committer = get_user_identity(config, kind="COMMITTER")
|
|
|
+ check_user_identity(committer)
|
|
|
+ c.committer = committer
|
|
|
+ if commit_timestamp is None:
|
|
|
+ commit_timestamp = time.time()
|
|
|
+ c.commit_time = int(commit_timestamp)
|
|
|
+ if commit_timezone is None:
|
|
|
+ commit_timezone = 0
|
|
|
+ c.commit_timezone = commit_timezone
|
|
|
+ if author is None:
|
|
|
+ author = get_user_identity(config, kind="AUTHOR")
|
|
|
+ c.author = author
|
|
|
+ check_user_identity(author)
|
|
|
+ if author_timestamp is None:
|
|
|
+ author_timestamp = commit_timestamp
|
|
|
+ c.author_time = int(author_timestamp)
|
|
|
+ if author_timezone is None:
|
|
|
+ author_timezone = commit_timezone
|
|
|
+ c.author_timezone = author_timezone
|
|
|
+ if encoding is None:
|
|
|
+ try:
|
|
|
+ encoding = config.get(("i18n",), "commitEncoding")
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+ if encoding is not None:
|
|
|
+ c.encoding = encoding
|
|
|
+
|
|
|
+ # Handle message (for MemoryRepo, we don't support callable messages)
|
|
|
+ if callable(message):
|
|
|
+ message = message(self, c)
|
|
|
+ if message is None:
|
|
|
+ raise ValueError("Message callback returned None")
|
|
|
+
|
|
|
+ if message is None:
|
|
|
+ raise ValueError("No commit message specified")
|
|
|
+
|
|
|
+ c.message = message
|
|
|
+
|
|
|
+ if ref is None:
|
|
|
+ # Create a dangling commit
|
|
|
+ c.parents = merge_heads
|
|
|
+ self.object_store.add_object(c)
|
|
|
+ else:
|
|
|
+ try:
|
|
|
+ old_head = self.refs[ref]
|
|
|
+ c.parents = [old_head, *merge_heads]
|
|
|
+ self.object_store.add_object(c)
|
|
|
+ ok = self.refs.set_if_equals(
|
|
|
+ ref,
|
|
|
+ old_head,
|
|
|
+ c.id,
|
|
|
+ message=b"commit: " + message,
|
|
|
+ committer=committer,
|
|
|
+ timestamp=commit_timestamp,
|
|
|
+ timezone=commit_timezone,
|
|
|
+ )
|
|
|
+ except KeyError:
|
|
|
+ c.parents = merge_heads
|
|
|
+ self.object_store.add_object(c)
|
|
|
+ ok = self.refs.add_if_new(
|
|
|
+ ref,
|
|
|
+ c.id,
|
|
|
+ message=b"commit: " + message,
|
|
|
+ committer=committer,
|
|
|
+ timestamp=commit_timestamp,
|
|
|
+ timezone=commit_timezone,
|
|
|
+ )
|
|
|
+ if not ok:
|
|
|
+ from .errors import CommitError
|
|
|
+
|
|
|
+ raise CommitError(f"{ref!r} changed during commit")
|
|
|
+
|
|
|
+ return c.id
|
|
|
+
|
|
|
@classmethod
|
|
|
def init_bare(cls, objects, refs, format: Optional[int] = None):
|
|
|
"""Create a new bare repository in memory.
|