@@ -2,7 +2,7 @@ use anyhow::{Context, Result};
22use clap:: Subcommand ;
33use std:: fs;
44
5- use crate :: utils:: { confirm , find_repo_root} ;
5+ use crate :: utils:: find_repo_root;
66
77const API_BASE : & str = "https://www.toptal.com/developers/gitignore/api" ;
88
@@ -35,24 +35,20 @@ pub fn run(cmd: IgnoreCommand) -> Result<()> {
3535 }
3636}
3737
38- fn add ( templates : & str , yes : bool , force : bool , dry_run : bool ) -> Result < ( ) > {
38+ fn add ( templates : & str , _yes : bool , _force : bool , dry_run : bool ) -> Result < ( ) > {
3939 let root = find_repo_root ( ) ?;
4040 let path = root. join ( ".gitignore" ) ;
4141
42- if path. exists ( ) && !force && !confirm ( ".gitignore already exists. Overwrite?" , yes) {
43- println ! ( "Aborted." ) ;
44- return Ok ( ( ) ) ;
45- }
46-
47- let content = resolve_templates ( templates) ?;
42+ let new_content = resolve_templates ( templates) ?;
43+ let merged = merge_gitignore ( & path, & new_content) ;
4844
4945 if dry_run {
50- println ! ( "[dry-run] Would write .gitignore:\n {content }" ) ;
46+ println ! ( "[dry-run] Would write .gitignore:\n {merged }" ) ;
5147 return Ok ( ( ) ) ;
5248 }
5349
54- fs:: write ( & path, content ) . context ( "Failed to write .gitignore" ) ?;
55- println ! ( "Generated .gitignore for: {templates}" ) ;
50+ fs:: write ( & path, merged ) . context ( "Failed to write .gitignore" ) ?;
51+ println ! ( "Updated .gitignore for: {templates}" ) ;
5652 Ok ( ( ) )
5753}
5854
@@ -113,6 +109,38 @@ fn list(filter: Option<&str>) -> Result<()> {
113109 Ok ( ( ) )
114110}
115111
112+ /// Merge new gitignore content into existing file, skipping lines already present.
113+ /// Preserves existing content and appends only new non-duplicate lines.
114+ fn merge_gitignore ( path : & std:: path:: Path , new_content : & str ) -> String {
115+ let existing = if path. exists ( ) {
116+ fs:: read_to_string ( path) . unwrap_or_default ( )
117+ } else {
118+ String :: new ( )
119+ } ;
120+
121+ let existing_lines: std:: collections:: HashSet < & str > = existing. lines ( ) . collect ( ) ;
122+
123+ let to_append: String = new_content
124+ . lines ( )
125+ . filter ( |line| !existing_lines. contains ( line) )
126+ . fold ( String :: new ( ) , |mut acc, line| {
127+ acc. push_str ( line) ;
128+ acc. push ( '\n' ) ;
129+ acc
130+ } ) ;
131+
132+ if to_append. trim ( ) . is_empty ( ) {
133+ return existing;
134+ }
135+
136+ let mut result = existing;
137+ if !result. ends_with ( '\n' ) && !result. is_empty ( ) {
138+ result. push ( '\n' ) ;
139+ }
140+ result. push_str ( & to_append) ;
141+ result
142+ }
143+
116144mod builtins {
117145 pub ( super ) const NAMES : & [ & str ] = & [ "agentic" ] ;
118146
0 commit comments