@@ -12,6 +12,7 @@ import { FileManager } from "./file-manager";
1212import { GitHubTrackerSettingTab } from "./settings-tab" ;
1313import { NoticeManager } from "./notice-manager" ;
1414import { GitHubKanbanView , KANBAN_VIEW_TYPE } from "./kanban-view" ;
15+ import { getEffectiveRepoSettings , stripProfileFieldsFromRepo } from "./util/settingsUtils" ;
1516
1617export default class GitHubTrackerPlugin extends Plugin {
1718 settings : GitHubTrackerSettings = DEFAULT_SETTINGS ;
@@ -215,6 +216,7 @@ export default class GitHubTrackerPlugin extends Plugin {
215216
216217 this . isSyncing = true ;
217218 try {
219+ const effectiveRepo = getEffectiveRepoSettings ( repo , this . settings ) ;
218220 this . noticeManager . info ( `Syncing repository: ${ repositoryName } ` ) ;
219221 const [ owner , repoName ] = repo . repository . split ( "/" ) ;
220222 if ( ! owner || ! repoName ) {
@@ -225,9 +227,9 @@ export default class GitHubTrackerPlugin extends Plugin {
225227 }
226228
227229 // Sync Issues
228- if ( repo . trackIssues ) {
230+ if ( effectiveRepo . trackIssues ) {
229231 this . noticeManager . debug (
230- `Fetching issues for ${ repo . repository } ` ,
232+ `Fetching issues for ${ effectiveRepo . repository } ` ,
231233 ) ;
232234 const allIssuesIncludingRecentlyClosed =
233235 await this . gitHubClient . fetchRepositoryIssues (
@@ -242,35 +244,35 @@ export default class GitHubTrackerPlugin extends Plugin {
242244 ) ;
243245
244246 // Decide which issues to filter based on settings
245- const issuesToFilter = repo . includeClosedIssues
247+ const issuesToFilter = effectiveRepo . includeClosedIssues
246248 ? allIssuesIncludingRecentlyClosed
247249 : openIssues ;
248250
249251 const filteredIssues = this . fileManager . filterIssues (
250- repo ,
252+ effectiveRepo ,
251253 issuesToFilter ,
252254 ) ;
253255
254256 this . noticeManager . debug (
255- `Processing ${ filteredIssues . length } issues (from ${ openIssues . length } open issues) for ${ repo . repository } ` ,
257+ `Processing ${ filteredIssues . length } issues (from ${ openIssues . length } open issues) for ${ effectiveRepo . repository } ` ,
256258 ) ;
257259
258260 const currentIssueNumbers = new Set (
259261 filteredIssues . map ( ( issue : any ) => issue . number . toString ( ) ) ,
260262 ) ;
261263
262264 await this . fileManager . createIssueFiles (
263- repo ,
265+ effectiveRepo ,
264266 filteredIssues ,
265267 allIssuesIncludingRecentlyClosed ,
266268 currentIssueNumbers ,
267269 ) ;
268270 }
269271
270272 // Sync Pull Requests
271- if ( repo . trackPullRequest ) {
273+ if ( effectiveRepo . trackPullRequest ) {
272274 this . noticeManager . debug (
273- `Fetching pull requests for ${ repo . repository } ` ,
275+ `Fetching pull requests for ${ effectiveRepo . repository } ` ,
274276 ) ;
275277
276278 const allPullRequestsIncludingRecentlyClosed =
@@ -287,25 +289,25 @@ export default class GitHubTrackerPlugin extends Plugin {
287289 ) ;
288290
289291 // Decide which pull requests to filter based on settings
290- const pullRequestsToFilter = repo . includeClosedPullRequests
292+ const pullRequestsToFilter = effectiveRepo . includeClosedPullRequests
291293 ? allPullRequestsIncludingRecentlyClosed
292294 : openPullRequests ;
293295
294296 const filteredPRs = this . fileManager . filterPullRequests (
295- repo ,
297+ effectiveRepo ,
296298 pullRequestsToFilter ,
297299 ) ;
298300
299301 this . noticeManager . debug (
300- `Processing ${ filteredPRs . length } pull requests (from ${ openPullRequests . length } open PRs) for ${ repo . repository } ` ,
302+ `Processing ${ filteredPRs . length } pull requests (from ${ openPullRequests . length } open PRs) for ${ effectiveRepo . repository } ` ,
301303 ) ;
302304
303305 const currentPRNumbers = new Set (
304306 filteredPRs . map ( ( pr : any ) => pr . number . toString ( ) ) ,
305307 ) ;
306308
307309 await this . fileManager . createPullRequestFiles (
308- repo ,
310+ effectiveRepo ,
309311 filteredPRs ,
310312 allPullRequestsIncludingRecentlyClosed ,
311313 currentPRNumbers ,
@@ -583,6 +585,48 @@ export default class GitHubTrackerPlugin extends Plugin {
583585 await this . saveData ( this . settings ) ;
584586 }
585587
588+ // Cleanup: Remove empty migrated profiles and reassign repos to default
589+ let needsCleanup = false ;
590+ for ( const repo of this . settings . repositories ) {
591+ if ( repo . profileId && repo . profileId . startsWith ( "migrated-" ) ) {
592+ const profile = this . settings . profiles . find ( p => p . id === repo . profileId ) ;
593+ if ( profile ) {
594+ const hasCustomValues = (
595+ profile . issueUpdateMode !== undefined ||
596+ profile . allowDeleteIssue !== undefined ||
597+ profile . issueFolder !== undefined ||
598+ profile . issueNoteTemplate !== undefined ||
599+ profile . issueContentTemplate !== undefined ||
600+ profile . useCustomIssueContentTemplate !== undefined ||
601+ profile . includeIssueComments !== undefined ||
602+ profile . pullRequestUpdateMode !== undefined ||
603+ profile . allowDeletePullRequest !== undefined ||
604+ profile . pullRequestFolder !== undefined ||
605+ profile . pullRequestNoteTemplate !== undefined ||
606+ profile . pullRequestContentTemplate !== undefined ||
607+ profile . useCustomPullRequestContentTemplate !== undefined ||
608+ profile . includePullRequestComments !== undefined ||
609+ profile . includeClosedIssues !== undefined ||
610+ profile . includeClosedPullRequests !== undefined ||
611+ profile . trackIssues !== undefined ||
612+ profile . trackPullRequest !== undefined
613+ ) ;
614+ if ( ! hasCustomValues ) {
615+ repo . profileId = "default" ;
616+ needsCleanup = true ;
617+ }
618+ }
619+ }
620+ }
621+ if ( needsCleanup ) {
622+ // Remove orphaned profiles (no repo references them)
623+ const usedProfileIds = new Set ( this . settings . repositories . map ( r => r . profileId ) ) ;
624+ this . settings . profiles = this . settings . profiles . filter (
625+ p => p . id === "default" || p . id === "default-project" || usedProfileIds . has ( p . id )
626+ ) ;
627+ await this . saveData ( this . settings ) ;
628+ }
629+
586630 // Migrate repos that still have old per-repo settings into their own profiles
587631 const defaultProfile = this . settings . profiles . find ( p => p . id === "default" ) ?? DEFAULT_REPOSITORY_PROFILE ;
588632 let needsSave = false ;
@@ -593,6 +637,24 @@ export default class GitHubTrackerPlugin extends Plugin {
593637 return repo ;
594638 }
595639
640+ // Check if repo actually has any profile-managed fields to migrate
641+ // (after stripProfileFieldsFromRepo they won't exist anymore)
642+ const hasProfileFields = (
643+ ( repo as any ) . issueUpdateMode !== undefined ||
644+ ( repo as any ) . issueFolder !== undefined ||
645+ ( repo as any ) . issueNoteTemplate !== undefined ||
646+ ( repo as any ) . pullRequestUpdateMode !== undefined ||
647+ ( repo as any ) . pullRequestFolder !== undefined ||
648+ ( repo as any ) . pullRequestNoteTemplate !== undefined
649+ ) ;
650+
651+ if ( ! hasProfileFields ) {
652+ // No profile-managed fields on repo — nothing to migrate
653+ if ( ! repo . profileId ) repo . profileId = "default" ;
654+ delete ( repo as any ) . ignoreGlobalSettings ;
655+ return repo ;
656+ }
657+
596658 const merged = Object . assign ( { } , DEFAULT_REPOSITORY_TRACKING , repo ) ;
597659
598660 // Check if repo has values that differ from the default profile
@@ -657,13 +719,44 @@ export default class GitHubTrackerPlugin extends Plugin {
657719 // Defaults first, then override with saved values
658720 this . settings . repositories = this . settings . repositories . map ( repo => {
659721 const merged = Object . assign ( { } , DEFAULT_REPOSITORY_TRACKING , repo ) ;
660- // Ensure critical fields are never undefined
661- if ( ! merged . issueFolder ) merged . issueFolder = DEFAULT_REPOSITORY_TRACKING . issueFolder ;
662- if ( ! merged . pullRequestFolder ) merged . pullRequestFolder = DEFAULT_REPOSITORY_TRACKING . pullRequestFolder ;
663722 if ( ! merged . profileId ) merged . profileId = "default" ;
664723 return merged ;
665724 } ) ;
666725
726+ // Migrate trackIssues/trackPullRequest from repos into their profiles
727+ let needsTrackMigration = false ;
728+ for ( const repo of this . settings . repositories ) {
729+ // Check if repo still has trackIssues/trackPullRequest explicitly set
730+ // (they are now optional and profile-managed)
731+ if ( ( repo as any ) . trackIssues !== undefined || ( repo as any ) . trackPullRequest !== undefined ) {
732+ const profileId = repo . profileId || "default" ;
733+ // Only migrate into non-default profiles (default profile would affect all repos)
734+ if ( profileId !== "default" ) {
735+ const profile = this . settings . profiles . find ( p => p . id === profileId ) ;
736+ if ( profile && profile . type === "repository" ) {
737+ if ( profile . trackIssues === undefined && ( repo as any ) . trackIssues !== undefined ) {
738+ profile . trackIssues = ( repo as any ) . trackIssues ;
739+ }
740+ if ( profile . trackPullRequest === undefined && ( repo as any ) . trackPullRequest !== undefined ) {
741+ profile . trackPullRequest = ( repo as any ) . trackPullRequest ;
742+ }
743+ }
744+ }
745+ // Remove migrated fields from repo so migration doesn't re-trigger
746+ delete ( repo as any ) . trackIssues ;
747+ delete ( repo as any ) . trackPullRequest ;
748+ needsTrackMigration = true ;
749+ }
750+ }
751+ if ( needsTrackMigration ) {
752+ await this . saveData ( this . settings ) ;
753+ }
754+
755+ // Hydrate profile-managed fields onto each repo from its profile
756+ this . settings . repositories = this . settings . repositories . map ( repo => {
757+ return getEffectiveRepoSettings ( repo , this . settings ) ;
758+ } ) ;
759+
667760 }
668761
669762 async saveSettings ( ) {
@@ -690,7 +783,22 @@ export default class GitHubTrackerPlugin extends Plugin {
690783 } ;
691784 }
692785
693- await this . saveData ( this . settings ) ;
786+ // Strip profile-managed fields from repos before persisting
787+ const dataToSave = {
788+ ...this . settings ,
789+ repositories : this . settings . repositories . map ( repo =>
790+ stripProfileFieldsFromRepo ( repo )
791+ ) ,
792+ } ;
793+ await this . saveData ( dataToSave ) ;
794+
795+ // Re-hydrate in-memory repos so profile changes take effect immediately
796+ // Mutate existing objects in place to preserve closure references in the UI
797+ for ( const repo of this . settings . repositories ) {
798+ const effective = getEffectiveRepoSettings ( repo , this . settings ) ;
799+ Object . assign ( repo , effective ) ;
800+ }
801+
694802 const token = this . getGitHubToken ( ) ;
695803 if ( token ) {
696804 this . gitHubClient ?. initializeClient ( ) ;
@@ -749,14 +857,14 @@ export default class GitHubTrackerPlugin extends Plugin {
749857
750858 try {
751859 for ( const repo of this . settings . repositories ) {
752- if ( ! repo . trackIssues ) continue ;
753-
754860 const [ owner , repoName ] = repo . repository . split ( "/" ) ;
755861 if ( ! owner || ! repoName ) continue ;
756862
757863 try {
864+ const effectiveRepo = getEffectiveRepoSettings ( repo , this . settings ) ;
865+ if ( ! effectiveRepo . trackIssues ) continue ;
758866 this . noticeManager . debug (
759- `Fetching issues for ${ repo . repository } ` ,
867+ `Fetching issues for ${ effectiveRepo . repository } ` ,
760868 ) ;
761869 const allIssuesIncludingRecentlyClosed =
762870 await this . gitHubClient . fetchRepositoryIssues (
@@ -771,12 +879,12 @@ export default class GitHubTrackerPlugin extends Plugin {
771879 ) ;
772880
773881 // Decide which issues to filter based on settings
774- const issuesToFilter = repo . includeClosedIssues
882+ const issuesToFilter = effectiveRepo . includeClosedIssues
775883 ? allIssuesIncludingRecentlyClosed
776884 : openIssues ;
777885
778886 const filteredIssues = this . fileManager . filterIssues (
779- repo ,
887+ effectiveRepo ,
780888 issuesToFilter ,
781889 ) ;
782890
@@ -790,14 +898,14 @@ export default class GitHubTrackerPlugin extends Plugin {
790898 ) ;
791899
792900 await this . fileManager . createIssueFiles (
793- repo ,
901+ effectiveRepo ,
794902 filteredIssues ,
795903 allIssuesIncludingRecentlyClosed ,
796904 currentIssueNumbers ,
797905 ) ;
798906
799907 this . noticeManager . debug (
800- `Processed ${ filteredIssues . length } open issues for ${ repo . repository } ` ,
908+ `Processed ${ filteredIssues . length } open issues for ${ effectiveRepo . repository } ` ,
801909 ) ;
802910 } catch ( repoError : unknown ) {
803911 this . noticeManager . error (
@@ -825,14 +933,14 @@ export default class GitHubTrackerPlugin extends Plugin {
825933
826934 try {
827935 for ( const repo of this . settings . repositories ) {
828- if ( ! repo . trackPullRequest ) continue ;
829-
830936 const [ owner , repoName ] = repo . repository . split ( "/" ) ;
831937 if ( ! owner || ! repoName ) continue ;
832938
833939 try {
940+ const effectiveRepo = getEffectiveRepoSettings ( repo , this . settings ) ;
941+ if ( ! effectiveRepo . trackPullRequest ) continue ;
834942 this . noticeManager . debug (
835- `Fetching pull requests for ${ repo . repository } ` ,
943+ `Fetching pull requests for ${ effectiveRepo . repository } ` ,
836944 ) ;
837945
838946 const allPullRequestsIncludingRecentlyClosed =
@@ -849,12 +957,12 @@ export default class GitHubTrackerPlugin extends Plugin {
849957 ) ;
850958
851959 // Decide which pull requests to filter based on settings
852- const pullRequestsToFilter = repo . includeClosedPullRequests
960+ const pullRequestsToFilter = effectiveRepo . includeClosedPullRequests
853961 ? allPullRequestsIncludingRecentlyClosed
854962 : openPullRequests ;
855963
856964 const filteredPRs = this . fileManager . filterPullRequests (
857- repo ,
965+ effectiveRepo ,
858966 pullRequestsToFilter ,
859967 ) ;
860968
@@ -869,14 +977,14 @@ export default class GitHubTrackerPlugin extends Plugin {
869977 ) ;
870978
871979 await this . fileManager . createPullRequestFiles (
872- repo ,
980+ effectiveRepo ,
873981 filteredPRs ,
874982 allPullRequestsIncludingRecentlyClosed ,
875983 currentPRNumbers ,
876984 ) ;
877985
878986 this . noticeManager . debug (
879- `Processed ${ filteredPRs . length } open pull requests for ${ repo . repository } ` ,
987+ `Processed ${ filteredPRs . length } open pull requests for ${ effectiveRepo . repository } ` ,
880988 ) ;
881989 } catch ( repoError : unknown ) {
882990 this . noticeManager . error (
0 commit comments