1pub mod blame;
2pub mod commit;
3mod hosting_provider;
4mod remote;
5pub mod repository;
6pub mod stash;
7pub mod status;
8
9pub use crate::hosting_provider::*;
10pub use crate::remote::*;
11use anyhow::{Context as _, Result};
12pub use git2 as libgit;
13use gpui::{Action, actions};
14pub use repository::WORK_DIRECTORY_REPO_PATH;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use std::ffi::OsStr;
18use std::fmt;
19use std::str::FromStr;
20use std::sync::LazyLock;
21
22pub static DOT_GIT: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".git"));
23pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore"));
24pub static FSMONITOR_DAEMON: LazyLock<&'static OsStr> =
25 LazyLock::new(|| OsStr::new("fsmonitor--daemon"));
26pub static LFS_DIR: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("lfs"));
27pub static COMMIT_MESSAGE: LazyLock<&'static OsStr> =
28 LazyLock::new(|| OsStr::new("COMMIT_EDITMSG"));
29pub static INDEX_LOCK: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("index.lock"));
30
31actions!(
32 git,
33 [
34 // per-hunk
35 /// Toggles the staged state of the hunk or status entry at cursor.
36 ToggleStaged,
37 /// Stage status entries between an anchor entry and the cursor.
38 StageRange,
39 /// Stages the current hunk and moves to the next one.
40 StageAndNext,
41 /// Unstages the current hunk and moves to the next one.
42 UnstageAndNext,
43 /// Restores the selected hunks to their original state.
44 #[action(deprecated_aliases = ["editor::RevertSelectedHunks"])]
45 Restore,
46 // per-file
47 /// Shows git blame information for the current file.
48 #[action(deprecated_aliases = ["editor::ToggleGitBlame"])]
49 Blame,
50 /// Stages the current file.
51 StageFile,
52 /// Unstages the current file.
53 UnstageFile,
54 // repo-wide
55 /// Stages all changes in the repository.
56 StageAll,
57 /// Unstages all changes in the repository.
58 UnstageAll,
59 /// Stashes all changes in the repository, including untracked files.
60 StashAll,
61 /// Pops the most recent stash.
62 StashPop,
63 /// Apply the most recent stash.
64 StashApply,
65 /// Restores all tracked files to their last committed state.
66 RestoreTrackedFiles,
67 /// Moves all untracked files to trash.
68 TrashUntrackedFiles,
69 /// Undoes the last commit, keeping changes in the working directory.
70 Uncommit,
71 /// Pushes commits to the remote repository.
72 Push,
73 /// Pushes commits to a specific remote branch.
74 PushTo,
75 /// Force pushes commits to the remote repository.
76 ForcePush,
77 /// Pulls changes from the remote repository.
78 Pull,
79 /// Fetches changes from the remote repository.
80 Fetch,
81 /// Fetches changes from a specific remote.
82 FetchFrom,
83 /// Creates a new commit with staged changes.
84 Commit,
85 /// Amends the last commit with staged changes.
86 Amend,
87 /// Enable the --signoff option.
88 Signoff,
89 /// Cancels the current git operation.
90 Cancel,
91 /// Expands the commit message editor.
92 ExpandCommitEditor,
93 /// Generates a commit message using AI.
94 GenerateCommitMessage,
95 /// Initializes a new git repository.
96 Init,
97 /// Opens all modified files in the editor.
98 OpenModifiedFiles,
99 /// Clones a repository.
100 Clone,
101 ]
102);
103
104/// Restores a file to its last committed state, discarding local changes.
105#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
106#[action(namespace = git, deprecated_aliases = ["editor::RevertFile"])]
107#[serde(deny_unknown_fields)]
108pub struct RestoreFile {
109 #[serde(default)]
110 pub skip_prompt: bool,
111}
112
113/// The length of a Git short SHA.
114pub const SHORT_SHA_LENGTH: usize = 7;
115
116#[derive(Clone, Copy, Eq, Hash, PartialEq)]
117pub struct Oid(libgit::Oid);
118
119impl Oid {
120 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
121 let oid = libgit::Oid::from_bytes(bytes).context("failed to parse bytes into git oid")?;
122 Ok(Self(oid))
123 }
124
125 #[cfg(any(test, feature = "test-support"))]
126 pub fn random(rng: &mut impl rand::Rng) -> Self {
127 let mut bytes = [0; 20];
128 rng.fill(&mut bytes);
129 Self::from_bytes(&bytes).unwrap()
130 }
131
132 pub fn as_bytes(&self) -> &[u8] {
133 self.0.as_bytes()
134 }
135
136 pub(crate) fn is_zero(&self) -> bool {
137 self.0.is_zero()
138 }
139
140 /// Returns this [`Oid`] as a short SHA.
141 pub fn display_short(&self) -> String {
142 self.to_string().chars().take(SHORT_SHA_LENGTH).collect()
143 }
144}
145
146impl FromStr for Oid {
147 type Err = anyhow::Error;
148
149 fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
150 libgit::Oid::from_str(s)
151 .context("parsing git oid")
152 .map(Self)
153 }
154}
155
156impl fmt::Debug for Oid {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 fmt::Display::fmt(self, f)
159 }
160}
161
162impl fmt::Display for Oid {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 self.0.fmt(f)
165 }
166}
167
168impl Serialize for Oid {
169 fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
170 where
171 S: serde::Serializer,
172 {
173 serializer.serialize_str(&self.0.to_string())
174 }
175}
176
177impl<'de> Deserialize<'de> for Oid {
178 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
179 where
180 D: serde::Deserializer<'de>,
181 {
182 let s = String::deserialize(deserializer)?;
183 s.parse::<Oid>().map_err(serde::de::Error::custom)
184 }
185}
186
187impl Default for Oid {
188 fn default() -> Self {
189 Self(libgit::Oid::zero())
190 }
191}
192
193impl From<Oid> for u32 {
194 fn from(oid: Oid) -> Self {
195 let bytes = oid.0.as_bytes();
196 debug_assert!(bytes.len() > 4);
197
198 let mut u32_bytes: [u8; 4] = [0; 4];
199 u32_bytes.copy_from_slice(&bytes[..4]);
200
201 u32::from_ne_bytes(u32_bytes)
202 }
203}
204
205impl From<Oid> for usize {
206 fn from(oid: Oid) -> Self {
207 let bytes = oid.0.as_bytes();
208 debug_assert!(bytes.len() > 8);
209
210 let mut u64_bytes: [u8; 8] = [0; 8];
211 u64_bytes.copy_from_slice(&bytes[..8]);
212
213 u64::from_ne_bytes(u64_bytes) as usize
214 }
215}