sync-to-github
Advanced tools
Comparing version 0.0.5 to 0.0.6
153
lib/sync.js
@@ -26,2 +26,5 @@ var GitHubApi = require('github'); | ||
* @option {string} message Message for committed changes | ||
* @option {boolean} [preserveRepoFiles] Perform only additive changes to the | ||
* target repo, i.e. do not delete existing files or directories inside | ||
* `repoPath`. | ||
* @option {string} [branch] Name of branch in repo to sync to, defaults | ||
@@ -46,30 +49,14 @@ * to `master`. This branch must already exist in repo. | ||
// TODO: support subdirectories and filtering by doing a traversal | ||
// and building blobs and trees as we visit. That algo may look somewhat | ||
// different from this one. | ||
return Promise.resolve().then(function() { | ||
// Create a tree holding the files | ||
// TODO: support subdirectories and filtering by doing a traversal | ||
// and building blobs and trees as we visit | ||
return createTreeForSingleDir(gitData, options.localPath); | ||
}).then(function(newTargetTree) { | ||
return getLatestCommit(gitData, options.branch).then(function(commit) { | ||
var treeCache = {}; | ||
return gitData('getTree', { sha: commit.tree.sha }).then(function(rootTree) { | ||
return parentTreesForPath(gitData, treeCache, rootTree, pathParts); | ||
return existingTreesForPath(gitData, rootTree, pathParts); | ||
}).then(function(treesForPath) { | ||
treesForPath.push(newTargetTree); | ||
return createTreeForPath(gitData, treesForPath, pathParts); | ||
return createNewTreeForPath(gitData, options, treesForPath, pathParts); | ||
}).then(function(newRootTree) { | ||
return gitData('createCommit', { | ||
message: options.message, | ||
tree: newRootTree.sha, | ||
parents: [commit.sha] | ||
}); | ||
}).then(function(newCommit) { | ||
return gitData('updateReference', { | ||
ref: 'heads/' + options.branch, | ||
sha: newCommit.sha | ||
}); | ||
}).then(function() { | ||
if (options.pullToBranch) { | ||
return createPullRequest(github, options); | ||
} | ||
return commitChanges(gitData, options, commit, newRootTree); | ||
}); | ||
@@ -184,7 +171,9 @@ }); | ||
* @param localPath {string} Local path to read all files from. | ||
* @param [baseTree] {object} Tree to base the new tree on. | ||
* @returns {Promise<Object>} Tree in github for the newly created directory | ||
* in the repository. | ||
*/ | ||
function createTreeForSingleDir(gitData, localPath) { | ||
function createTreeForSingleDir(gitData, localPath, baseTree) { | ||
// For each file in the directory | ||
var pathExists = {}; | ||
return fs.readdirAsync(localPath).then(function(filenames) { | ||
@@ -213,2 +202,3 @@ return Promise.all(filenames.map(function(filename) { | ||
}).then(function(response) { | ||
pathExists[filename] = true; | ||
return { | ||
@@ -224,2 +214,10 @@ path: filename, | ||
}).then(function(children) { | ||
if (baseTree) { | ||
// We're basing this tree off an existing one. Bring in any children from | ||
// the base that were not created in the new tree. | ||
children = children.concat( | ||
treeChildrenForCreateRequest(baseTree).filter(function(child) { | ||
return !pathExists[child.path]; | ||
})); | ||
} | ||
return gitData('createTree', { tree: children }); | ||
@@ -229,2 +227,37 @@ }); | ||
/** | ||
* Recursively create the the new git tree that effects changes along the | ||
* given path. | ||
* | ||
* @param gitData {function} Wrapper to call the `gitdata` endpoint. | ||
* @param options {object} Options for the sync request | ||
* @param treesForPath {object[]} Array of trees from root to deepest change, | ||
* with the last element being the newest change. | ||
* @param pathParts {string[]} Array of path components from root to deepest. | ||
* @returns {Promise<Object>} Tree in github for the newly created directory | ||
* in the repository. | ||
*/ | ||
function createNewTreeForPath(gitData, options, treesForPath, pathParts) { | ||
return Promise.resolve().then(function() { | ||
var existingTree = null; | ||
if (treesForPath.length === pathParts.length + 1) { | ||
// If there is a tree for every part of the path, there will be one more | ||
// tree than part because there is no path part for the root. | ||
debug(options, 'Target path already exists'); | ||
existingTree = treesForPath.splice(-1)[0]; | ||
} | ||
if (treesForPath.length === pathParts.length) { | ||
return createTreeForSingleDir( | ||
gitData, options.localPath, | ||
(existingTree && options.preserveRepoFiles) ? existingTree : null); | ||
} else { | ||
throw new Error( | ||
'Could not find existing parent dir for repo path: ' + | ||
options.repoPath); | ||
} | ||
}).then(function(newTargetTree) { | ||
treesForPath.push(newTargetTree); | ||
return createTreeForPath(gitData, treesForPath, pathParts); | ||
}); | ||
} | ||
@@ -264,5 +297,3 @@ /** | ||
var newTreeChildren = parentTree.tree.map(function(child) { | ||
return _.pick(child, ['path', 'mode', 'type', 'sha']); | ||
}); | ||
var newTreeChildren = treeChildrenForCreateRequest(parentTree); | ||
return gitData('createTree', { tree: newTreeChildren }).then(function(newTree) { | ||
@@ -277,2 +308,43 @@ return createTreeForPath( | ||
/** | ||
* @param tree {object} A tree object as received from github | ||
* @returns {object[]} An array of tree entries that are appropriate to | ||
* upload to github for a tree creation request. | ||
*/ | ||
function treeChildrenForCreateRequest(tree) { | ||
return tree.tree.map(function(child) { | ||
return _.pick(child, ['path', 'mode', 'type', 'sha']); | ||
}); | ||
} | ||
/** | ||
* Commit a new tree to the branch to make the sync effective. | ||
* | ||
* @param gitData {function} | ||
* @param options {object} | ||
* @param latestCommit {object} latest commit on branch | ||
* @param newRootTree {object} new root tree to commit | ||
* @returns {Promise} Complete when changes are committed | ||
*/ | ||
function commitChanges(gitData, options, latestCommit, newRootTree) { | ||
// TODO: perhaps do something more sophisticated / robust to collisions | ||
// like branch and merge. | ||
return Promise.resolve().then(function() { | ||
return gitData('createCommit', { | ||
message: options.message, | ||
tree: newRootTree.sha, | ||
parents: [latestCommit.sha] | ||
}); | ||
}).then(function(newCommit) { | ||
return gitData('updateReference', { | ||
ref: 'heads/' + options.branch, | ||
sha: newCommit.sha | ||
}); | ||
}).then(function() { | ||
if (options.pullToBranch) { | ||
return createPullRequest(github, options); | ||
} | ||
}); | ||
} | ||
/** | ||
* @param gitData {function} Wrapper to call the `gitdata` endpoint. | ||
@@ -290,17 +362,17 @@ * @param branchName {string} Name of the branch to get the latest commit for. | ||
/** | ||
* Fetch tree information along a path. | ||
* Fetch tree information along a path, as far as it exists. | ||
* | ||
* @param gitData {function} Wrapper to call the `gitdata` endpoint. | ||
* @param treeCache {object} Cache of path -> tree so we don't have to fetch | ||
* the same tree multiple times from git. May be initialized to empty. | ||
* @param rootTree {object} Tree from git representing the root of the repo. | ||
* @param pathParts {string[]} Components of the path to get the trees for. | ||
* @returns {Promise<Object[]>} Trees from root to the parent of the deepest | ||
* component of `pathParts`. | ||
* @param [treeCache] {object} Cache of path -> tree so we don't have to fetch | ||
* the same tree multiple times from git. May be initialized to empty. | ||
* @returns {Promise<Object[]>} Trees from root to the deepest existing | ||
* component of `pathParts`. Will have a length <= `pathParts.length` | ||
*/ | ||
function parentTreesForPath(gitData, treeCache, rootTree, pathParts) { | ||
function existingTreesForPath(gitData, rootTree, pathParts, treeCache) { | ||
treeCache = treeCache || {}; | ||
treeCache[rootTree.sha] = rootTree; | ||
if (pathParts.length <= 1) { | ||
// We stop here. The deepest component may not even exist, but we don't | ||
// need to return it since we're getting parents. | ||
if (pathParts.length === 0) { | ||
// No where left to go! | ||
return [rootTree]; | ||
@@ -316,3 +388,4 @@ } | ||
if (!nextTreeInfo) { | ||
throw new Error('Path not found in tree: ' + path); | ||
// This is the end of the line. | ||
return [rootTree]; | ||
} | ||
@@ -322,3 +395,3 @@ | ||
return getTree(gitData, treeCache, nextTreeInfo.sha).then(function(nextTree) { | ||
return parentTreesForPath(gitData, treeCache, nextTree, remainingPath); | ||
return existingTreesForPath(gitData, nextTree, remainingPath, treeCache); | ||
}).then(function(subTree) { | ||
@@ -369,3 +442,3 @@ return [rootTree].concat(subTree); | ||
// an outstanding pull request. | ||
// TODO: update pull request with new message | ||
// TODO: update pull request with new message? | ||
console.log('Pull request for branch already exists, ignoring.'); | ||
@@ -380,3 +453,3 @@ } else { | ||
syncToGitHub: syncToGitHub, | ||
parentTreesForPath: parentTreesForPath | ||
existingTreesForPath: existingTreesForPath | ||
}; |
{ | ||
"name": "sync-to-github", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "Easily sync a directory of files to a GitHub repo using the GitHub API", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17836
414