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