3333const assert = require ( "chai" ) . assert ;
3434const co = require ( "co" ) ;
3535const colors = require ( "colors" ) ;
36+ const fs = require ( "fs-promise" ) ;
3637const NodeGit = require ( "nodegit" ) ;
3738
39+ const CloseUtil = require ( "./close_util" ) ;
3840const ConfigUtil = require ( "./config_util" ) ;
3941const DiffUtil = require ( "./diff_util" ) ;
4042const GitUtil = require ( "./git_util" ) ;
@@ -44,11 +46,19 @@ const RepoStatus = require("./repo_status");
4446const SparseCheckoutUtil = require ( "./sparse_checkout_util" ) ;
4547const StatusUtil = require ( "./status_util" ) ;
4648const SubmoduleUtil = require ( "./submodule_util" ) ;
49+ const SubmoduleConfigUtil = require ( "./submodule_config_util" ) ;
4750const SubmoduleRebaseUtil = require ( "./submodule_rebase_util" ) ;
4851const TreeUtil = require ( "./tree_util" ) ;
4952const UserError = require ( "./user_error" ) ;
5053
5154const Commit = NodeGit . Commit ;
55+ const Change = TreeUtil . Change ;
56+ const FILEMODE = NodeGit . TreeEntry . FILEMODE ;
57+
58+ const MAGIC_DELETED_SHA = NodeGit . Oid . fromString (
59+ "de1e7ed0de1e7ed0de1e7ed0de1e7ed0de1e7ed0" ) ;
60+
61+ const GITMODULES = SubmoduleConfigUtil . modulesFileName ;
5262
5363/**
5464 * Return the IDs of tress reflecting the current state of the index and
@@ -107,6 +117,62 @@ exports.makeLogMessage = co.wrap(function *(repo) {
107117WIP on ${ branchDesc } : ${ GitUtil . shortSha ( head . id ( ) . tostrS ( ) ) } ${ message } ` ;
108118} ) ;
109119
120+
121+ function getNewGitModuleSha ( diff ) {
122+ const numDeltas = diff . numDeltas ( ) ;
123+ for ( let i = 0 ; i < numDeltas ; ++ i ) {
124+ const delta = diff . getDelta ( i ) ;
125+ // We assume that the user hasn't deleted the .gitmodules file.
126+ // That would be bonkers.
127+ const file = delta . newFile ( ) ;
128+ const path = file . path ( ) ;
129+ if ( path === GITMODULES ) {
130+ return delta . newFile ( ) . id ( ) ;
131+ }
132+ }
133+ // diff does not include .gitmodules
134+ return null ;
135+ }
136+
137+
138+ const stashGitModules = co . wrap ( function * ( repo , headTree ) {
139+ assert . instanceOf ( repo , NodeGit . Repository ) ;
140+
141+ const result = { } ;
142+ // RepoStatus throws away the diff new sha, and rather than hack
143+ // it, since it's used all over the codebase, we'll just redo the
144+ // diffs for this one file.
145+
146+ const workdirToTreeDiff =
147+ yield NodeGit . Diff . treeToWorkdir ( repo ,
148+ headTree ,
149+ { pathspec : [ GITMODULES ] } ) ;
150+
151+
152+ const newWorkdir = getNewGitModuleSha ( workdirToTreeDiff ) ;
153+ if ( newWorkdir !== null ) {
154+ result . workdir = newWorkdir ;
155+ }
156+
157+ const indexToTreeDiff =
158+ yield NodeGit . Diff . treeToIndex ( repo ,
159+ headTree ,
160+ yield repo . index ( ) ,
161+ { pathspec : [ GITMODULES ] } ) ;
162+
163+ const newIndex = getNewGitModuleSha ( indexToTreeDiff ) ;
164+ if ( newIndex !== null ) {
165+ result . staged = newIndex ;
166+ }
167+
168+ yield NodeGit . Checkout . tree ( repo , headTree , {
169+ paths : [ GITMODULES ] ,
170+ checkoutStrategy : NodeGit . Checkout . STRATEGY . FORCE ,
171+ } ) ;
172+
173+ return result ;
174+ } ) ;
175+
110176/**
111177 * Save the state of the submodules in the specified, `repo` having the
112178 * specified `status` and clean the sub-repositories to match their respective
@@ -149,6 +215,8 @@ exports.save = co.wrap(function *(repo, status, includeUntracked, message) {
149215 const subRepos = { } ; // name to submodule open repo
150216
151217 const sig = yield ConfigUtil . defaultSignature ( repo ) ;
218+ const head = yield repo . getHeadCommit ( ) ;
219+ const headTree = yield head . getTree ( ) ;
152220
153221 // First, we process the submodules. If a submodule is open and dirty,
154222 // we'll create the stash commits in its repo, populate `subResults` with
@@ -178,34 +246,42 @@ report this. Continuing stash anyway.`);
178246
179247 if ( null === wd ) {
180248 // closed submodule
181- if ( sub . commit . sha === sub . index . sha ) {
182- // ... with no staged changes
183- return ; // RETURN
184- }
185- // This is a case that regular git stash doesn't really have
186- // to handle. In a normal stash commit, the tree points
187- // to the working directory tree, but here, there is no working
188- // directory. But if there were, we would want to have
189- // this commit checked out.
190-
191- const subRepo = yield SubmoduleUtil . getRepo ( repo , name ) ;
249+ if ( sub . index === null || sub . index . sha === null ) {
250+ // deleted submodule
251+ stashId = MAGIC_DELETED_SHA ;
252+ yield NodeGit . Checkout . tree ( repo , headTree , {
253+ paths : [ name ] ,
254+ checkoutStrategy : NodeGit . Checkout . STRATEGY . FORCE ,
255+ } ) ;
256+ } else {
257+ if ( sub . commit . sha === sub . index . sha ) {
258+ // ... with no staged changes
259+ return ; // RETURN
260+ }
261+ // This is a case that regular git stash doesn't really have
262+ // to handle. In a normal stash commit, the tree points
263+ // to the working directory tree, but here, there is no working
264+ // directory. But if there were, we would want to have
265+ // this commit checked out.
192266
193- const subCommit = yield Commit . lookup ( subRepo , sub . commit . sha ) ;
194- const indexCommit = yield Commit . lookup ( subRepo , sub . index . sha ) ;
195- const indexTree = yield indexCommit . getTree ( ) ;
196- stashId = yield Commit . create ( subRepo ,
197- null ,
198- sig ,
199- sig ,
200- null ,
201- "stash" ,
202- indexTree ,
203- 4 ,
204- [ subCommit ,
205- indexCommit ,
206- indexCommit ,
207- indexCommit ] ) ;
267+ const subRepo = yield SubmoduleUtil . getRepo ( repo , name ) ;
208268
269+ const subCommit = yield Commit . lookup ( subRepo , sub . commit . sha ) ;
270+ const indexCommit = yield Commit . lookup ( subRepo , sub . index . sha ) ;
271+ const indexTree = yield indexCommit . getTree ( ) ;
272+ stashId = yield Commit . create ( subRepo ,
273+ null ,
274+ sig ,
275+ sig ,
276+ null ,
277+ "stash" ,
278+ indexTree ,
279+ 4 ,
280+ [ subCommit ,
281+ indexCommit ,
282+ indexCommit ,
283+ indexCommit ] ) ;
284+ }
209285 } else {
210286 // open submodule
211287 if ( sub . commit . sha !== sub . index . sha &&
@@ -318,13 +394,42 @@ commit to have two parents`);
318394 subResults [ name ] = stashId . tostrS ( ) ;
319395 // Record the values we've created.
320396
321- subChanges [ name ] = new TreeUtil . Change (
322- stashId ,
323- NodeGit . TreeEntry . FILEMODE . COMMIT ) ;
397+ subChanges [ name ] = new TreeUtil . Change ( stashId , FILEMODE . COMMIT ) ;
324398 } ) ) ;
325399
326- const head = yield repo . getHeadCommit ( ) ;
327- const headTree = yield head . getTree ( ) ;
400+ const parents = [ head ] ;
401+
402+ const gitModulesChanges = yield stashGitModules ( repo , headTree ) ;
403+ if ( gitModulesChanges ) {
404+ if ( gitModulesChanges . workdir ) {
405+ subChanges [ GITMODULES ] = new Change ( gitModulesChanges . workdir ,
406+ FILEMODE . BLOB ) ;
407+ }
408+ if ( gitModulesChanges . staged ) {
409+ const indexChanges = { } ;
410+ Object . assign ( indexChanges , subChanges ) ;
411+
412+ indexChanges [ GITMODULES ] = new Change ( gitModulesChanges . staged ,
413+ FILEMODE . BLOB ) ;
414+
415+
416+ const indexTree = yield TreeUtil . writeTree ( repo , headTree ,
417+ indexChanges ) ;
418+ const indexParent = yield Commit . create ( repo ,
419+ null ,
420+ sig ,
421+ sig ,
422+ null ,
423+ "stash" ,
424+ indexTree ,
425+ 1 ,
426+ [ head ] ) ;
427+
428+ const indexParentCommit = yield Commit . lookup ( repo , indexParent ) ;
429+ parents . push ( indexParentCommit ) ;
430+ }
431+ }
432+
328433 const subsTree = yield TreeUtil . writeTree ( repo , headTree , subChanges ) ;
329434 const stashId = yield Commit . create ( repo ,
330435 null ,
@@ -333,8 +438,8 @@ commit to have two parents`);
333438 null ,
334439 "stash" ,
335440 subsTree ,
336- 1 ,
337- [ head ] ) ;
441+ parents . length ,
442+ parents ) ;
338443
339444 const stashSha = stashId . tostrS ( ) ;
340445
@@ -438,19 +543,63 @@ exports.apply = co.wrap(function *(repo, id, reinstateIndex) {
438543 assert . isString ( id ) ;
439544
440545 const commit = yield repo . getCommit ( id ) ;
546+ const repoIndex = yield repo . index ( ) ;
441547
442548 // TODO: patch libgit2/nodegit: the commit object returned from `parent`
443549 // isn't properly configured with a `repo` object, and attempting to use it
444550 // in `getSubmodulesForCommit` will fail, so we have to look it up.
445551
446552 const parentId = ( yield commit . parent ( 0 ) ) . id ( ) ;
447553 const parent = yield repo . getCommit ( parentId ) ;
554+ const parentTree = yield parent . getTree ( ) ;
555+
448556 const baseSubs = yield SubmoduleUtil . getSubmodulesForCommit ( repo ,
449557 parent ,
450558 null ) ;
559+
560+ let indexSubs = baseSubs ;
561+ if ( commit . parentcount ( ) > 1 ) {
562+ const parent2Id = ( yield commit . parent ( 1 ) ) . id ( ) ;
563+ const parent2 = yield repo . getCommit ( parent2Id ) ;
564+ indexSubs = yield SubmoduleUtil . getSubmodulesForCommit ( repo ,
565+ parent2 ,
566+ null ) ;
567+ }
568+
451569 const newSubs = yield SubmoduleUtil . getSubmodulesForCommit ( repo ,
452570 commit ,
453571 null ) ;
572+
573+ const toDelete = [ ] ;
574+ yield Object . keys ( baseSubs ) . map ( co . wrap ( function * ( name ) {
575+ if ( newSubs [ name ] === undefined ) {
576+ if ( fs . existsSync ( name ) ) {
577+ // sub deleted in working tree
578+ toDelete . push ( name ) ;
579+ }
580+ }
581+ } ) ) ;
582+
583+ CloseUtil . close ( repo , repo . workdir ( ) , toDelete , false ) ;
584+ for ( const name of toDelete ) {
585+ yield fs . rmdir ( name ) ;
586+ }
587+
588+ yield Object . keys ( baseSubs ) . map ( co . wrap ( function * ( name ) {
589+ if ( indexSubs [ name ] === undefined ) {
590+ // sub deleted in the index
591+ yield repoIndex . removeByPath ( name ) ;
592+ }
593+ } ) ) ;
594+
595+ // apply gitmodules diff
596+ const headTree = yield commit . getTree ( ) ;
597+ yield NodeGit . Checkout . tree ( repo , headTree , {
598+ paths : [ GITMODULES ] ,
599+ baseline : parentTree ,
600+ checkoutStrategy : NodeGit . Checkout . STRATEGY . MERGE ,
601+ } ) ;
602+
454603 const opener = new Open . Opener ( repo , null ) ;
455604 let result = { } ;
456605 const index = { } ;
@@ -550,8 +699,6 @@ for debugging, is:`, e);
550699 }
551700 } ) ) ;
552701
553- const repoIndex = yield repo . index ( ) ;
554-
555702 if ( null !== result ) {
556703 for ( let name of Object . keys ( index ) ) {
557704 const entry = new NodeGit . IndexEntry ( ) ;
0 commit comments