Skip to content

Commit

Permalink
Support amending of initial commit
Browse files Browse the repository at this point in the history
Signed-off-by: Nigel Westbury <nigelipse@miegel.org>
  • Loading branch information
westbury committed Sep 23, 2019
1 parent 8196e9d commit 5322e24
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 32 deletions.
17 changes: 14 additions & 3 deletions packages/git/src/browser/git-scm-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,11 @@ export namespace GitScmProvider {

export class GitAmendSupport implements ScmAmendSupport {

constructor(protected readonly repository: Repository, protected readonly git: Git) { }
constructor(
protected readonly repository: Repository,
protected readonly git: Git) { }

public async getInitialAmendingCommits(amendingHeadCommitSha: string, latestCommitSha: string): Promise<ScmCommit[]> {
public async getInitialAmendingCommits(amendingHeadCommitSha: string, latestCommitSha: string | undefined): Promise<ScmCommit[]> {
const commits = await this.git.log(
this.repository,
{
Expand All @@ -414,7 +416,16 @@ export class GitAmendSupport implements ScmAmendSupport {
}

public async reset(commit: string): Promise<void> {
await this.git.exec(this.repository, ['reset', commit, '--soft']);
if (commit === 'HEAD~' && await this.isHeadInitialCommit()) {
await this.git.exec(this.repository, ['update-ref', '-d', 'HEAD']);
} else {
await this.git.exec(this.repository, ['reset', commit, '--soft']);
}
}

protected async isHeadInitialCommit(): Promise<boolean> {
const result = await this.git.revParse(this.repository, { ref: 'HEAD~' });
return !result;
}

public async getLastCommit(): Promise<ScmCommit | undefined> {
Expand Down
74 changes: 46 additions & 28 deletions packages/scm/src/browser/scm-amend-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export class ScmAmendComponent extends React.Component<ScmAmendComponentProps, S
}

async fetchStatusAndSetState(): Promise<void> {
const storageKey = this.getStorageKey();

const nextCommit = await this.getLastCommit();
if (nextCommit && this.state.lastCommit && nextCommit.commit.id === this.state.lastCommit.commit.id) {
// No change here
Expand All @@ -119,32 +121,37 @@ export class ScmAmendComponent extends React.Component<ScmAmendComponentProps, S
// the view just updates without transition.
this.setState({ amendingCommits: [], lastCommit: nextCommit });
} else {
const amendingCommits = this.state.amendingCommits.concat([]); // copy the array

const direction: 'up' | 'down' = this.transitionHint === 'amend' ? 'up' : 'down';
switch (this.transitionHint) {
case 'amend':
if (this.state.lastCommit) {
amendingCommits.push(this.state.lastCommit);

const serializedState = JSON.stringify({
amendingHeadCommitSha: amendingCommits[0].commit.id,
latestCommitSha: nextCommit ? nextCommit.commit.id : undefined
});
this.props.storageService.setData<string | undefined>(storageKey, serializedState);
}
break;
case 'unamend':
amendingCommits.pop();
if (amendingCommits.length === 0) {
this.props.storageService.setData<string | undefined>(storageKey, undefined);
} else {
const serializedState = JSON.stringify({
amendingHeadCommitSha: amendingCommits[0].commit.id,
latestCommitSha: nextCommit ? nextCommit.commit.id : undefined
});
this.props.storageService.setData<string | undefined>(storageKey, serializedState);
}
break;
}

if (this.state.lastCommit && nextCommit) {
const direction: 'up' | 'down' = this.transitionHint === 'amend' ? 'up' : 'down';
const transitionData = { direction, previousLastCommit: this.state.lastCommit };
const amendingCommits = this.state.amendingCommits.concat([]); // copy the array
switch (this.transitionHint) {
case 'amend':
if (this.state.lastCommit) {
amendingCommits.push(this.state.lastCommit);

const storageKey = this.getStorageKey();
const serializedState = JSON.stringify({
amendingHeadCommitSha: amendingCommits[0].commit.id,
latestCommitSha: nextCommit.commit.id
});
this.props.storageService.setData<string | undefined>(storageKey, serializedState);
}
break;
case 'unamend':
amendingCommits.pop();
if (amendingCommits.length === 0) {
const storageKey = this.getStorageKey();
this.props.storageService.setData<string | undefined>(storageKey, undefined);
}
break;
}

this.setState({ lastCommit: nextCommit, amendingCommits, transition: { ...transitionData, state: 'start' } });
this.onNextFrame(() => {
this.setState({ transition: { ...transitionData, state: 'transitioning' } });
Expand All @@ -157,7 +164,7 @@ export class ScmAmendComponent extends React.Component<ScmAmendComponentProps, S
TRANSITION_TIME_MS);
} else {
// No previous last commit so no transition
this.setState({ transition: { state: 'none' }, lastCommit: nextCommit });
this.setState({ transition: { state: 'none' }, amendingCommits, lastCommit: nextCommit });
}
}

Expand All @@ -176,14 +183,14 @@ export class ScmAmendComponent extends React.Component<ScmAmendComponentProps, S
// Restore list of commits from saved amending head commit up through parents until the
// current commit. (If we don't reach the current commit, the repository has been changed in such
// a way then unamending commits can no longer be done).
if (storedState && lastCommit) {
if (storedState) {
const { amendingHeadCommitSha, latestCommitSha } = JSON.parse(storedState);
if (lastCommit.id !== latestCommitSha) {
if (!this.commitsAreEqual(lastCommit, latestCommitSha)) {
// The head commit in the repository has changed. It is not the same commit that was the
// head commit after the last 'amend'.
return [];
}
const commits = await this.props.scmAmendSupport.getInitialAmendingCommits(amendingHeadCommitSha, lastCommit.id);
const commits = await this.props.scmAmendSupport.getInitialAmendingCommits(amendingHeadCommitSha, lastCommit ? lastCommit.id : undefined);

const amendingCommitPromises = commits.map(async commit => {
const avatar = await this.props.avatarService.getAvatar(commit.authorEmail);
Expand All @@ -199,6 +206,17 @@ export class ScmAmendComponent extends React.Component<ScmAmendComponentProps, S
return REPOSITORY_STORAGE_KEY + ':' + this.props.repository.provider.rootUri;
}

/**
* Commits are equal if the ids are equal or if both are undefined.
* (If a commit is undefined, it represents the initial empty state of a repository,
* before the inital commit).
*/
private commitsAreEqual(lastCommit: ScmCommit | undefined, savedLastCommitId: string | undefined): boolean {
return lastCommit
? lastCommit.id === savedLastCommitId
: savedLastCommitId === undefined;
}

/**
* This function will update the 'model' (lastCommit, amendingCommits) only
* when the repository sees the last commit change.
Expand Down
2 changes: 1 addition & 1 deletion packages/scm/src/browser/scm-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export interface ScmCommit {
}

export interface ScmAmendSupport {
getInitialAmendingCommits(amendingHeadCommitSha: string, latestCommitSha: string): Promise<ScmCommit[]>
getInitialAmendingCommits(amendingHeadCommitId: string, latestCommitId: string | undefined): Promise<ScmCommit[]>
getMessage(commit: string): Promise<string>;
reset(commit: string): Promise<void>;
getLastCommit(): Promise<ScmCommit | undefined>;
Expand Down

0 comments on commit 5322e24

Please sign in to comment.