Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Fetch/push/pull #121

Merged
merged 59 commits into from
Apr 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
2f729b0
Dummy out the push/pull component.
joshaber Apr 1, 2016
6de1b74
We don't really need to hold on to this.
joshaber Apr 1, 2016
7e19776
Dummy out the PushPull view model.
joshaber Apr 1, 2016
debc9c5
Add type declarations for fetch + cred.
joshaber Apr 4, 2016
fa0ecbc
Dummy out the fetch call.
joshaber Apr 4, 2016
45dd1e7
Add type more type information for Remote.
joshaber Apr 4, 2016
1903721
Add function to get remotes.
joshaber Apr 4, 2016
af01b1f
Try fetching with a remote.
joshaber Apr 4, 2016
d987ab5
It helps if your type declarations are right.
joshaber Apr 4, 2016
e47185c
s/onClick/onclick
joshaber Apr 4, 2016
c437822
Silence standard.
joshaber Apr 4, 2016
2982ee2
Alot more type declarations.
joshaber Apr 4, 2016
dd6206f
Stub out push.
joshaber Apr 4, 2016
ddc0c98
Maybe some pulling too?
joshaber Apr 4, 2016
1564ca0
Display errors and a progress spinner.
joshaber Apr 4, 2016
feeea3d
Don't shadow the username.
joshaber Apr 4, 2016
7b5a428
Provide a reasonable default refspec.
joshaber Apr 4, 2016
98f8127
A nu todo.
joshaber Apr 4, 2016
d456f0e
Fixed the type declaration for #upstream.
joshaber Apr 5, 2016
cf87b43
Build a reasonable default push refspec.
joshaber Apr 5, 2016
425d29c
Consume the token the GitHub package provides.
joshaber Apr 5, 2016
5cdc397
Pass the token down to the push/pull view model.
joshaber Apr 5, 2016
375d209
This should really be optional.
joshaber Apr 5, 2016
094e418
Pass the token through.
joshaber Apr 5, 2016
c2a58b2
Fetch from all remotes.
joshaber Apr 5, 2016
e2d816c
Pokemon.
joshaber Apr 5, 2016
5e8356f
Don't need this anymore.
joshaber Apr 5, 2016
6e5508e
Use a more appropriate error.
joshaber Apr 5, 2016
1a3f7bb
Support pushing new branches.
joshaber Apr 5, 2016
755dc1b
Don't need to do it anymore. Because we todid it.
joshaber Apr 5, 2016
47f7c89
Type declaration for Config#setString.
joshaber Apr 5, 2016
40331b2
Write config entries after pushing a new branch.
joshaber Apr 5, 2016
2ffc19b
Add commands to fetch/pull/push.
joshaber Apr 5, 2016
052cf34
Keep track of inflight requests in the view model.
joshaber Apr 5, 2016
440f0b3
Snapshots are readonly.
joshaber Apr 5, 2016
3bb5dfb
Start some specs.
joshaber Apr 5, 2016
df6ee69
Actually do something.
joshaber Apr 5, 2016
23bc620
Don't try to set the token if we don't have a repo.
joshaber Apr 6, 2016
b566686
Catch not found values to a null value.
joshaber Apr 6, 2016
d54ac72
Let's try it without actually pushing.
joshaber Apr 6, 2016
5877fea
Put 'cloned' in the path so it's easier to see which part is failing.
joshaber Apr 6, 2016
bd67397
Clone bare to setup the remote repo.
joshaber Apr 6, 2016
dc6e85d
Verify the commit was pushed.
joshaber Apr 6, 2016
d62fc5d
Just use FETCH_HEAD.
joshaber Apr 6, 2016
5573504
Put a commit on the remote to pull.
joshaber Apr 6, 2016
060757c
Test the events.
joshaber Apr 6, 2016
ad4e0ac
Just return null here and it'll do The Right Thing.
joshaber Apr 6, 2016
443772f
Don't need to log this anymore.
joshaber Apr 6, 2016
13b250c
Always change in flight requests count even if the action throws.
joshaber Apr 6, 2016
992e8f7
Clear the error after a timeout.
joshaber Apr 6, 2016
a2f964e
s/requestsInFlight/hasRequestsInFlight
joshaber Apr 6, 2016
1f976e6
Type declaration for TransferProgress.
joshaber Apr 6, 2016
a223d43
Push doesn't need to be async anymore.
joshaber Apr 6, 2016
d2bbf89
Calculate the percentage for the progress callback.
joshaber Apr 6, 2016
7437031
Make fetch progress more right.
joshaber Apr 6, 2016
210eb65
Provide a push service.
joshaber Apr 6, 2016
ec54fbd
Expose #push on the file list view model.
joshaber Apr 6, 2016
15605c4
Oh JSON.
joshaber Apr 7, 2016
8ae51a7
Merge the upstream branch instead of FETCH_HEAD.
joshaber Apr 7, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions interfaces/nodegit.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,48 @@ declare module 'nodegit' {
static default(repo: Repository): Signature;
}

declare class Remote {
name(): string;
url(): string;

getPushRefspecs(): Promise<Array<string>>;
push(refspecs: ?Array<string>, options: Object): Promise<void>;

fetch(refspecs: ?Array<string>, options: Object, message: string): Promise<void>;

disconnect(): Promise<void>;
stop(): void;
}

declare class FetchOptions {

}

declare class Cred {
static userpassPlaintextNew(username: string, password: string): Cred;
static sshKeyFromAgent(username: string): Cred;
static defaultNew(): Cred;
}

declare class Reference {
name(): string;
shorthand(): string;
}

declare class Branch {
static upstream(branch: Reference): Promise<?Reference>;
}

declare class Config {
getStringBuf(name: string): ?string;
setString(name: string, value: string): Promise<void>;
}

declare class TransferProgress {
totalObjects(): number;
receivedObjects(): number;
}

declare class Repository {
static open(path: string): Promise<Repository>;

Expand All @@ -129,5 +171,18 @@ declare module 'nodegit' {
createCommit(updateRef: string, author: Signature, committer: Signature, message: string, oid: Oid, parents: ?Array<Commit>): Promise<Oid>;

getStatusExt(): Promise<Array<StatusFile>>;

getRemotes(): Array<string>;
getRemote(name: string): Promise<?Remote>;

getReference(name: string): Promise<?Reference>;
getCurrentBranch(): Promise<Reference>;

fetch(remote: string | Remote, options: Object | FetchOptions): Promise<void>;

configSnapshot(): Promise<Config>;
config(): Promise<Config>;

mergeBranches(to: string | Reference, from: string | Reference, signature?: Signature): Promise<Oid>;
}
}
9 changes: 4 additions & 5 deletions lib/file-list-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import etch from 'etch'
import {CompositeDisposable} from 'atom'
// $FlowFixMe: Yes, we know this isn't a React component :\
import CommitBoxComponent from './commit-box-component'
import CommitBoxViewModel from './commit-box-view-model'
// $FlowFixMe: Yes, we know this isn't a React component :\
import FileSummaryComponent from './file-summary-component'
// $FlowFixMe: Yes, we know this isn't a React component :\
import PushPullComponent from './push-pull-component'

import type FileListViewModel from './file-list-view-model'

Expand All @@ -16,7 +17,6 @@ type FileListComponentProps = {fileListViewModel: FileListViewModel}
export default class FileListComponent {
viewModel: FileListViewModel;
element: HTMLElement;
commitBoxViewModel: CommitBoxViewModel;
subscriptions: CompositeDisposable;

constructor (props: FileListComponentProps) {
Expand All @@ -32,8 +32,6 @@ export default class FileListComponent {
acceptProps ({fileListViewModel}: FileListComponentProps): Promise<void> {
this.viewModel = fileListViewModel

this.commitBoxViewModel = fileListViewModel.commitBoxViewModel

let updatePromise = Promise.resolve()
if (this.element) {
updatePromise = etch.update(this)
Expand Down Expand Up @@ -79,6 +77,7 @@ export default class FileListComponent {
render () {
return (
<div className='git-Panel' tabIndex='-1'>
<PushPullComponent viewModel={this.viewModel.pushPullViewModel}/>
<header className='git-Panel-item is-header'>Changes</header>
<div className='git-Panel-item is-flexible git-FileList'>{
this.viewModel.getFileDiffViewModels().map((viewModel, index) =>
Expand All @@ -91,7 +90,7 @@ export default class FileListComponent {
toggleAction={c => this.onToggleFileSummary(c)}/>
)
}</div>
<CommitBoxComponent viewModel={this.commitBoxViewModel}/>
<CommitBoxComponent viewModel={this.viewModel.commitBoxViewModel}/>
</div>
)
}
Expand Down
11 changes: 11 additions & 0 deletions lib/file-list-view-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import {Emitter, CompositeDisposable} from 'atom'
import FileDiffViewModel from './file-diff-view-model'
import CommitBoxViewModel from './commit-box-view-model'
import PushPullViewModel from './push-pull-view-model'

import type {Disposable} from 'atom'
import type GitStore from './git-store'
Expand All @@ -13,6 +14,7 @@ export default class FileListViewModel {
selectedIndex: number;
emitter: Emitter;
commitBoxViewModel: CommitBoxViewModel;
pushPullViewModel: PushPullViewModel;
subscriptions: CompositeDisposable;

constructor (gitStore: GitStore) {
Expand All @@ -21,6 +23,7 @@ export default class FileListViewModel {
this.emitter = new Emitter()
this.subscriptions = new CompositeDisposable()
this.commitBoxViewModel = new CommitBoxViewModel(gitStore)
this.pushPullViewModel = new PushPullViewModel(gitStore)

this.gitStore.onDidUpdate(() => this.emitUpdateEvent())
}
Expand Down Expand Up @@ -100,4 +103,12 @@ export default class FileListViewModel {
getFileDiffViewModels (): Array<FileDiffViewModel> {
return this.gitStore.getFiles().map(fileDiff => new FileDiffViewModel(fileDiff))
}

setToken (t: string) {
this.pushPullViewModel.setToken(t)
}

push (): Promise<void> {
return this.pushPullViewModel.push()
}
}
19 changes: 18 additions & 1 deletion lib/git-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ import type {ChangeType} from './git-store'

type GitState = {panelVisible: boolean}

type GitPushFunction = () => Promise<void>

export default class GitPackage {
subscriptions: CompositeDisposable;
state: GitState;
changesPanel: ?Panel<FileListViewModel>;
statusBarTile: ?Tile<StatusBarViewModel>;
fileListViewModel: FileListViewModel;
fileListViewModel: ?FileListViewModel;
gitStore: GitStore;
token: ?string;

constructor () {
this.subscriptions = new CompositeDisposable()
Expand Down Expand Up @@ -199,6 +202,20 @@ export default class GitPackage {
this.statusBarTile = tile
}

consumeGitHubToken (token: string) {
this.token = token

if (!this.hasRepository()) return

this.getFileListViewModel().setToken(token)
}

provideGitPush (): ?GitPushFunction {
if (!this.hasRepository()) return null

return () => this.getFileListViewModel().push()
}

createDiffPaneItem ({uri, pending}: {uri: string, pending: boolean}): DiffViewModel {
const pathName = uri.replace(DiffURI, '')
const gitStore = this.getGitStore()
Expand Down
194 changes: 194 additions & 0 deletions lib/git-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,4 +540,198 @@ export default class GitService {
}
})
}

getRemotes (): Promise<Array<string>> {
return Git.Repository
.open(this.repoPath)
.then(repo => repo.getRemotes())
}

createNetworkOptions (remote: Git.Remote, {username, password}: {username: string, password: string}, progress: (progress: number) => void): Object {
let authAttempts = 0
const credentialsCallback = (url, user) => {
if (authAttempts > 2) {
remote.stop()
remote.disconnect()
return Git.Cred.defaultNew()
}

authAttempts++
return Git.Cred.userpassPlaintextNew(username, password)
}
return {
callbacks: {
transferProgress: (transferProgress: Git.TransferProgress) => {
progress(transferProgress.receivedObjects() / transferProgress.totalObjects())
},
credentials: credentialsCallback
}
}
}

static NoRemoteErrorName (): string {
return 'GitService.noRemote'
}

static NoBranchErrorName (): string {
return 'GitService.noBranch'
}

fetch (remote: string, creds: {username: string, password: string}, progress: (progress: number) => void): Promise<void> {
return Git.Repository
.open(this.repoPath)
.then(repo => repo.getRemote(remote))
.then(remote_ => {
if (!remote_) {
const error = new Error()
error.name = GitService.NoRemoteErrorName()
return Promise.reject(error)
}

const remote = remote_
return remote.fetch(null, this.createNetworkOptions(remote, creds, progress), `Fetch from ${remote}`)
})
}

async pull (branchName: string, creds: {username: string, password: string}, progress: (progress: number) => void): Promise<void> {
// const upstream = await Git.Branch.upstream(branch)

const remote = await this.getBranchRemote(branchName)
if (!remote) {
const error = new Error()
error.name = GitService.NoRemoteErrorName()
return Promise.reject(error)
}

await this.fetch(remote, creds, progress)

const repo = await Git.Repository.open(this.repoPath)

const branch = await repo.getReference(branchName)
if (!branch) {
const error = new Error()
error.name = GitService.NoBranchErrorName()
return Promise.reject(error)
}

const upstream = await Git.Branch.upstream(branch)
if (!upstream) {
const error = new Error()
error.name = GitService.NoBranchErrorName()
return Promise.reject(error)
}

await repo.mergeBranches(branchName, upstream.name())
}

static NoRemotesErrorName (): string {
return 'GitService.noRemotes'
}

static TooManyRemotesErrorName (): string {
return 'GitService.tooManyRemotes'
}

async pushNewBranch (branch: string, creds: {username: string, password: string}, progress: (progress: number) => void): Promise<void> {
const remotes = await this.getRemotes()
if (!remotes.length) {
const error = new Error()
error.name = GitService.NoRemotesErrorName()
return Promise.reject(error)
}

// TODO: At some point we can let the user choose a remote, but for now
// let's just solve the easy case.
if (remotes.length > 1) {
const error = new Error()
error.name = GitService.TooManyRemotesErrorName()
return Promise.reject(error)
}

const remoteName = remotes[0]

const repo = await Git.Repository.open(this.repoPath)
const remote = await repo.getRemote(remoteName)
if (!remote) {
const error = new Error()
error.name = GitService.NoRemoteErrorName()
return Promise.reject(error)
}

const refspec = `refs/heads/${branch}`
const refspecs = [`${refspec}:${refspec}`]
await remote.push(refspecs, this.createNetworkOptions(remote, creds, progress))

const config = await repo.config()
await config.setString(`branch.${branch}.remote`, remoteName)
await config.setString(`branch.${branch}.merge`, refspec)
}

async push (branch: string, creds: {username: string, password: string}, progress: (progress: number) => void): Promise<void> {
const remoteName = await this.getBranchRemote(branch)
if (!remoteName) {
return this.pushNewBranch(branch, creds, progress)
}

const repo = await Git.Repository.open(this.repoPath)
const remote = await repo.getRemote(remoteName)
if (!remote) {
const error = new Error()
error.name = GitService.NoRemoteErrorName()
return Promise.reject(error)
}

const refspecs = await this.getPushRefspecs(branch)
return remote.push(refspecs, this.createNetworkOptions(remote, creds, progress))
}

async getPushRefspecs (branchName: string): Promise<Array<string>> {
const remoteName = await this.getBranchRemote(branchName)
if (!remoteName) {
const error = new Error()
error.name = GitService.NoRemoteErrorName()
return Promise.reject(error)
}

const repo = await Git.Repository.open(this.repoPath)
const remote = await repo.getRemote(remoteName)
if (!remote) {
const error = new Error()
error.name = GitService.NoRemoteErrorName()
return Promise.reject(error)
}

const refspecs = await remote.getPushRefspecs()
// If we don't have any refspecs configured, then try to construct a
// reasonable default. Ideally libgit2 would do this for us.
if (refspecs.length > 0) return refspecs

const branch = await repo.getReference(branchName)
if (!branch) {
const error = new Error()
error.name = GitService.NoBranchErrorName()
return Promise.reject(error)
}

const upstream = await Git.Branch.upstream(branch)
if (!upstream) {
const error = new Error()
error.name = GitService.NoBranchErrorName()
return Promise.reject(error)
}

// The upstream branch's name takes the form of:
// refs/remotes/REMOTE_NAME/BRANCH_NAME
// We just want the last part of that.
const upstreamBranchName = upstream.name().replace(`refs/remotes/${remoteName}/`, '')
return [`refs/heads/${branchName}:refs/heads/${upstreamBranchName}`]
}

getBranchRemote (name: string): Promise<?string> {
return Git.Repository
.open(this.repoPath)
.then(repo => repo.configSnapshot())
.then(config => config.getStringBuf(`branch.${name}.remote`))
.catch(e => null)
}
}
Loading