Skip to content

Commit 63be538

Browse files
authored
feat: implements revert feature (#148)
1 parent 3fe2eaa commit 63be538

File tree

5 files changed

+1046
-0
lines changed

5 files changed

+1046
-0
lines changed

index.d.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,45 @@ export declare function discoverRepository(path: string, signal?: AbortSignal |
17111711
* ```
17121712
*/
17131713
export declare function cloneRepository(url: string, path: string, options?: RepositoryCloneOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise<Repository>
1714+
/**
1715+
* Options for revert behavior.
1716+
*
1717+
* Controls how a revert is performed when applying the inverse of a commit.
1718+
*
1719+
* @example
1720+
* ```ts
1721+
* import { openRepository } from 'es-git';
1722+
*
1723+
* const repo = await openRepository('./path/to/repo');
1724+
* const head = repo.head().target()!;
1725+
* const commit = repo.getCommit(head);
1726+
*
1727+
* // Simple revert
1728+
* repo.revert(commit);
1729+
* repo.cleanupState();
1730+
*
1731+
* // Revert a merge commit selecting the first parent as mainline
1732+
* repo.revert(commit, { mainline: 1 });
1733+
* repo.cleanupState();
1734+
*
1735+
* // Prevent working tree changes (dry run) but compute conflicts
1736+
* repo.revert(commit, { checkoutOptions: { dryRun: true } });
1737+
* repo.cleanupState();
1738+
* ```
1739+
*/
1740+
export interface RevertOptions {
1741+
/**
1742+
* Parent number for merge commits (1-based).
1743+
*
1744+
* When reverting a merge commit, the mainline parent is the one you want to
1745+
* revert to. The mainline is the branch into which the merge was made.
1746+
*/
1747+
mainline?: number
1748+
/** Options for merge conflict resolution. */
1749+
mergeOptions?: MergeOptions
1750+
/** Options for checkout behavior when updating working directory. */
1751+
checkoutOptions?: CheckoutOptions
1752+
}
17141753
/**
17151754
* Flags for the revparse.
17161755
* - `Single` : The spec targeted a single object.
@@ -5433,6 +5472,16 @@ export declare class Repository {
54335472
* ```
54345473
*
54355474
* @returns The current state of this repository.
5475+
*
5476+
* @example
5477+
* ```ts
5478+
* import { openRepository } from 'es-git';
5479+
*
5480+
* const repo = await openRepository('./repo');
5481+
* console.log(repo.state()); // e.g., 'Clean'
5482+
* // After a revert/merge/cherry-pick, state can be 'Revert'/'Merge' etc.
5483+
* // Use repo.cleanupState() to return to 'Clean' when done handling.
5484+
* ```
54365485
*/
54375486
state(): RepositoryState
54385487
/**
@@ -5597,8 +5646,97 @@ export declare class Repository {
55975646
* cleanupState(): void;
55985647
* }
55995648
* ```
5649+
*
5650+
* @example
5651+
* ```ts
5652+
* import { openRepository } from 'es-git';
5653+
*
5654+
* const repo = await openRepository('./repo');
5655+
* // After revert or merge operations:
5656+
* if (repo.state() !== 'Clean') {
5657+
* repo.cleanupState();
5658+
* }
5659+
* ```
56005660
*/
56015661
cleanupState(): void
5662+
/**
5663+
* Reverts the given commit, applying the inverse of its changes to the
5664+
* HEAD commit and the working directory.
5665+
*
5666+
* @category Repository/Methods
5667+
* @signature
5668+
* ```ts
5669+
* class Repository {
5670+
* revert(
5671+
* commit: Commit,
5672+
* options?: RevertOptions | undefined | null,
5673+
* ): void;
5674+
* }
5675+
* ```
5676+
*
5677+
* @param {Commit} commit - The commit to revert.
5678+
* @param {RevertOptions} [options] - Options for the revert operation.
5679+
* @throws {Error} If the commit is a merge commit and no mainline is specified.
5680+
* @throws {Error} If there are conflicts during the revert operation.
5681+
*
5682+
* @example
5683+
* ```ts
5684+
* import { openRepository } from 'es-git';
5685+
*
5686+
* const repo = await openRepository('./path/to/repo');
5687+
* const last = repo.head().target()!;
5688+
* const commit = repo.getCommit(last);
5689+
*
5690+
* // Revert and update working tree
5691+
* repo.revert(commit);
5692+
* repo.cleanupState();
5693+
*
5694+
* // Revert a merge commit: specify the mainline parent
5695+
* // repo.revert(mergeCommit, { mainline: 1 });
5696+
* // repo.cleanupState();
5697+
* ```
5698+
*/
5699+
revert(commit: Commit, options?: RevertOptions | undefined | null): void
5700+
/**
5701+
* Reverts the given commit against the given "our" commit, producing an
5702+
* index that reflects the result of the revert.
5703+
*
5704+
* The returned index must be written to disk for the changes to take effect.
5705+
*
5706+
* @category Repository/Methods
5707+
* @signature
5708+
* ```ts
5709+
* class Repository {
5710+
* revertCommit(
5711+
* revertCommit: Commit,
5712+
* ourCommit: Commit,
5713+
* mainline: number,
5714+
* mergeOptions?: MergeOptions | undefined | null,
5715+
* ): Index;
5716+
* }
5717+
* ```
5718+
*
5719+
* @param {Commit} revertCommit - The commit to revert.
5720+
* @param {Commit} ourCommit - The commit to revert against (usually HEAD).
5721+
* @param {number} mainline - The parent of the revert commit, if it is a merge (1-based).
5722+
* @param {MergeOptions} [mergeOptions] - Options for merge conflict resolution.
5723+
* @returns The index result.
5724+
*
5725+
* @example
5726+
* ```ts
5727+
* import { openRepository } from 'es-git';
5728+
*
5729+
* const repo = await openRepository('./path/to/repo');
5730+
* const head = repo.head().target()!;
5731+
* const our = repo.getCommit(head);
5732+
* const target = repo.getCommit(head);
5733+
*
5734+
* // Compute a revert index and apply to working tree
5735+
* const idx = repo.revertCommit(target, our, 0);
5736+
* repo.checkoutIndex(idx);
5737+
* ```
5738+
*/
5739+
revertCommit(revertCommit: Commit, ourCommit: Commit, mainline: number, mergeOptions?: MergeOptions | undefined | null): Index
56025740
/**
56035741
* Execute a rev-parse operation against the `spec` listed.
56045742
*

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod rebase;
1919
pub mod reference;
2020
pub mod remote;
2121
pub mod repository;
22+
pub mod revert;
2223
pub mod revparse;
2324
pub mod revwalk;
2425
pub mod signature;

src/repository.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,16 @@ impl Repository {
341341
/// ```
342342
///
343343
/// @returns The current state of this repository.
344+
///
345+
/// @example
346+
/// ```ts
347+
/// import { openRepository } from 'es-git';
348+
///
349+
/// const repo = await openRepository('./repo');
350+
/// console.log(repo.state()); // e.g., 'Clean'
351+
/// // After a revert/merge/cherry-pick, state can be 'Revert'/'Merge' etc.
352+
/// // Use repo.cleanupState() to return to 'Clean' when done handling.
353+
/// ```
344354
pub fn state(&self) -> RepositoryState {
345355
self.inner.state().into()
346356
}
@@ -543,6 +553,17 @@ impl Repository {
543553
/// cleanupState(): void;
544554
/// }
545555
/// ```
556+
///
557+
/// @example
558+
/// ```ts
559+
/// import { openRepository } from 'es-git';
560+
///
561+
/// const repo = await openRepository('./repo');
562+
/// // After revert or merge operations:
563+
/// if (repo.state() !== 'Clean') {
564+
/// repo.cleanupState();
565+
/// }
566+
/// ```
546567
pub fn cleanup_state(&self) -> crate::Result<()> {
547568
self.inner.cleanup_state()?;
548569
Ok(())

src/revert.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use crate::checkout::CheckoutOptions;
2+
use crate::commit::Commit;
3+
use crate::index::Index;
4+
use crate::merge::MergeOptions;
5+
use crate::repository::Repository;
6+
use napi_derive::napi;
7+
8+
#[napi(object)]
9+
/// Options for revert behavior.
10+
///
11+
/// Controls how a revert is performed when applying the inverse of a commit.
12+
///
13+
/// @example
14+
/// ```ts
15+
/// import { openRepository } from 'es-git';
16+
///
17+
/// const repo = await openRepository('./path/to/repo');
18+
/// const head = repo.head().target()!;
19+
/// const commit = repo.getCommit(head);
20+
///
21+
/// // Simple revert
22+
/// repo.revert(commit);
23+
/// repo.cleanupState();
24+
///
25+
/// // Revert a merge commit selecting the first parent as mainline
26+
/// repo.revert(commit, { mainline: 1 });
27+
/// repo.cleanupState();
28+
///
29+
/// // Prevent working tree changes (dry run) but compute conflicts
30+
/// repo.revert(commit, { checkoutOptions: { dryRun: true } });
31+
/// repo.cleanupState();
32+
/// ```
33+
pub struct RevertOptions {
34+
/// Parent number for merge commits (1-based).
35+
///
36+
/// When reverting a merge commit, the mainline parent is the one you want to
37+
/// revert to. The mainline is the branch into which the merge was made.
38+
pub mainline: Option<u32>,
39+
/// Options for merge conflict resolution.
40+
pub merge_options: Option<MergeOptions>,
41+
/// Options for checkout behavior when updating working directory.
42+
pub checkout_options: Option<CheckoutOptions>,
43+
}
44+
45+
impl From<RevertOptions> for git2::RevertOptions<'static> {
46+
fn from(value: RevertOptions) -> Self {
47+
let mut opts = git2::RevertOptions::new();
48+
49+
if let Some(mainline) = value.mainline {
50+
opts.mainline(mainline);
51+
}
52+
53+
if let Some(merge_opts) = value.merge_options {
54+
opts.merge_opts(merge_opts.into());
55+
}
56+
57+
if let Some(checkout_opts) = value.checkout_options {
58+
opts.checkout_builder(checkout_opts.into());
59+
}
60+
61+
opts
62+
}
63+
}
64+
65+
#[napi]
66+
impl Repository {
67+
#[napi]
68+
/// Reverts the given commit, applying the inverse of its changes to the
69+
/// HEAD commit and the working directory.
70+
///
71+
/// @category Repository/Methods
72+
/// @signature
73+
/// ```ts
74+
/// class Repository {
75+
/// revert(
76+
/// commit: Commit,
77+
/// options?: RevertOptions | undefined | null,
78+
/// ): void;
79+
/// }
80+
/// ```
81+
///
82+
/// @param {Commit} commit - The commit to revert.
83+
/// @param {RevertOptions} [options] - Options for the revert operation.
84+
/// @throws {Error} If the commit is a merge commit and no mainline is specified.
85+
/// @throws {Error} If there are conflicts during the revert operation.
86+
///
87+
/// @example
88+
/// ```ts
89+
/// import { openRepository } from 'es-git';
90+
///
91+
/// const repo = await openRepository('./path/to/repo');
92+
/// const last = repo.head().target()!;
93+
/// const commit = repo.getCommit(last);
94+
///
95+
/// // Revert and update working tree
96+
/// repo.revert(commit);
97+
/// repo.cleanupState();
98+
///
99+
/// // Revert a merge commit: specify the mainline parent
100+
/// // repo.revert(mergeCommit, { mainline: 1 });
101+
/// // repo.cleanupState();
102+
/// ```
103+
pub fn revert(&self, commit: &Commit, options: Option<RevertOptions>) -> crate::Result<()> {
104+
let mut git_options = options.map(Into::into);
105+
106+
self
107+
.inner
108+
.revert(&commit.inner, git_options.as_mut())
109+
.map_err(crate::Error::from)?;
110+
111+
Ok(())
112+
}
113+
114+
#[napi]
115+
/// Reverts the given commit against the given "our" commit, producing an
116+
/// index that reflects the result of the revert.
117+
///
118+
/// The returned index must be written to disk for the changes to take effect.
119+
///
120+
/// @category Repository/Methods
121+
/// @signature
122+
/// ```ts
123+
/// class Repository {
124+
/// revertCommit(
125+
/// revertCommit: Commit,
126+
/// ourCommit: Commit,
127+
/// mainline: number,
128+
/// mergeOptions?: MergeOptions | undefined | null,
129+
/// ): Index;
130+
/// }
131+
/// ```
132+
///
133+
/// @param {Commit} revertCommit - The commit to revert.
134+
/// @param {Commit} ourCommit - The commit to revert against (usually HEAD).
135+
/// @param {number} mainline - The parent of the revert commit, if it is a merge (1-based).
136+
/// @param {MergeOptions} [mergeOptions] - Options for merge conflict resolution.
137+
/// @returns The index result.
138+
///
139+
/// @example
140+
/// ```ts
141+
/// import { openRepository } from 'es-git';
142+
///
143+
/// const repo = await openRepository('./path/to/repo');
144+
/// const head = repo.head().target()!;
145+
/// const our = repo.getCommit(head);
146+
/// const target = repo.getCommit(head);
147+
///
148+
/// // Compute a revert index and apply to working tree
149+
/// const idx = repo.revertCommit(target, our, 0);
150+
/// repo.checkoutIndex(idx);
151+
/// ```
152+
pub fn revert_commit(
153+
&self,
154+
revert_commit: &Commit,
155+
our_commit: &Commit,
156+
mainline: u32,
157+
merge_options: Option<MergeOptions>,
158+
) -> crate::Result<Index> {
159+
let opts = merge_options.map(git2::MergeOptions::from);
160+
let inner = self
161+
.inner
162+
.revert_commit(&revert_commit.inner, &our_commit.inner, mainline, opts.as_ref())?;
163+
Ok(Index { inner })
164+
}
165+
}

0 commit comments

Comments
 (0)