• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

levibostian / new-deployment-tool / 15758275762

19 Jun 2025 12:49PM UTC coverage: 71.903% (-1.3%) from 73.169%
15758275762

push

github

levibostian
build: follow best-practice of tool and setup github actions workflow to run the tool for deployments and PRs

67 of 86 branches covered (77.91%)

Branch coverage included in aggregate %.

583 of 818 relevant lines covered (71.27%)

9.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.4
/lib/git.ts
1
import { exec } from "./exec.ts"
2
import { Exec } from "./exec.ts"
3
import { GitHubCommit } from "./github-api.ts"
4
import * as log from "./log.ts"
2✔
5

6
export interface Git {
7
  checkoutBranch: (
8
    { exec, branch, createBranchIfNotExist }: { exec: Exec; branch: string; createBranchIfNotExist: boolean },
9
  ) => Promise<void>
10
  merge: (
11
    { exec, branchToMergeIn, commitTitle, commitMessage, fastForward }: {
12
      exec: Exec
13
      branchToMergeIn: string
14
      commitTitle: string
15
      commitMessage: string
16
      fastForward?: "--no-ff" | "--ff-only"
17
    },
18
  ) => Promise<void>
19
  pull: ({ exec }: { exec: Exec }) => Promise<void>
20
  setUser: (
21
    { exec, name, email }: { exec: Exec; name: string; email: string },
22
  ) => Promise<void>
23
  squash: (
24
    { exec, branchToSquash, branchMergingInto, commitTitle, commitMessage }: {
25
      exec: Exec
26
      branchToSquash: string
27
      branchMergingInto: string
28
      commitTitle: string
29
      commitMessage: string
30
    },
31
  ) => Promise<void>
32
  rebase: (
33
    { exec, branchToRebaseOnto }: { exec: Exec; branchToRebaseOnto: string },
34
  ) => Promise<void>
35
  getLatestCommitsSince({ exec, commit }: { exec: Exec; commit: GitHubCommit }): Promise<GitHubCommit[]>
36
  getLatestCommitOnBranch({ exec, branch }: { exec: Exec; branch: string }): Promise<GitHubCommit>
37
  createLocalBranchFromRemote: ({ exec, branch }: { exec: Exec; branch: string }) => Promise<void>
38
}
39

40
const checkoutBranch = async (
2✔
41
  { exec, branch, createBranchIfNotExist }: { exec: Exec; branch: string; createBranchIfNotExist: boolean },
2✔
42
): Promise<void> => {
43
  await exec.run({
16✔
44
    command: `git checkout ${createBranchIfNotExist ? "-b " : ""}${branch}`,
16✔
45
    input: undefined,
16✔
46
  })
16✔
47
}
2✔
48

49
const merge = async (
2✔
50
  { exec, branchToMergeIn, commitTitle, commitMessage, fastForward }: {
2✔
51
    exec: Exec
52
    branchToMergeIn: string
53
    commitTitle: string
54
    commitMessage: string
55
    fastForward?: "--no-ff" | "--ff-only"
56
  },
2✔
57
): Promise<void> => {
58
  await exec.run({
5✔
59
    command: `git merge ${branchToMergeIn} -m "${commitTitle}" -m "${commitMessage}" ${fastForward || ""}`,
×
60
    input: undefined,
5✔
61
  })
5✔
62
}
2✔
63

64
const pull = async ({ exec }: { exec: Exec }): Promise<void> => {
2✔
65
  await exec.run({
8✔
66
    command: `git pull`,
8✔
67
    input: undefined,
8✔
68
  })
8✔
69
}
2✔
70

71
const setUser = async (
2✔
72
  { exec, name, email }: { exec: Exec; name: string; email: string },
2✔
73
): Promise<void> => {
74
  await exec.run({
5✔
75
    command: `git config user.name "${name}"`,
5✔
76
    input: undefined,
5✔
77
  })
5✔
78

79
  await exec.run({
5✔
80
    command: `git config user.email "${email}"`,
5✔
81
    input: undefined,
5✔
82
  })
5✔
83
}
2✔
84

85
// Squash all commits of a branch into 1 commit
86
const squash = async (
2✔
87
  { exec, branchToSquash, branchMergingInto, commitTitle, commitMessage }: {
2✔
88
    exec: Exec
89
    branchToSquash: string
90
    branchMergingInto: string
91
    commitTitle: string
92
    commitMessage: string
93
  },
2✔
94
): Promise<void> => {
95
  // We need to find out how many commits 1 branch is ahead of the other to find out how many unique commits there are.
96
  const { stdout } = await exec.run({
3✔
97
    command: `git rev-list --count ${branchMergingInto}..${branchToSquash}`,
3✔
98
    input: undefined,
3✔
99
  })
3✔
100

101
  const numberOfCommitsAheadOfBranchMergingInto = parseInt(stdout.trim())
3✔
102

103
  if (numberOfCommitsAheadOfBranchMergingInto === 0) {
×
104
    log.message(`Branches ${branchToSquash} and ${branchMergingInto} are already up to date. No commits to squash.`)
×
105
    return
×
106
  }
×
107

108
  // Now that we know how many commits are ahead, we can squash all of those commits into 1 commit.
109
  await exec.run({
3✔
110
    command: `git reset --soft HEAD~${numberOfCommitsAheadOfBranchMergingInto}`,
3✔
111
    input: undefined,
3✔
112
  })
3✔
113
  await exec.run({
3✔
114
    command: `git commit -m "${commitTitle}" -m "${commitMessage}"`,
3✔
115
    input: undefined,
3✔
116
  })
3✔
117
}
2✔
118

119
const rebase = async (
2✔
120
  { exec, branchToRebaseOnto }: { exec: Exec; branchToRebaseOnto: string },
2✔
121
): Promise<void> => {
122
  await exec.run({
4✔
123
    command: `git rebase ${branchToRebaseOnto}`,
4✔
124
    input: undefined,
4✔
125
  })
4✔
126
}
2✔
127

128
const getLatestCommitsSince = async (
2✔
129
  { exec, commit }: { exec: Exec; commit: GitHubCommit },
2✔
130
): Promise<GitHubCommit[]> => {
131
  const { stdout } = await exec.run({
5✔
132
    command: `git log --pretty=format:"%H|%s|%ci" ${commit.sha}..HEAD`,
5✔
133
    input: undefined,
5✔
134
  })
5✔
135

136
  return stdout.trim().split("\n").map((commitString) => {
5✔
137
    const [sha, message, dateString] = commitString.split("|")
8✔
138

139
    return { sha, message, date: new Date(dateString) }
40✔
140
  })
5✔
141
}
2✔
142

143
const getLatestCommitOnBranch = async (
2✔
144
  { exec, branch }: { exec: Exec; branch: string },
2✔
145
): Promise<GitHubCommit> => {
146
  const { stdout } = await exec.run({
5✔
147
    command: `git log -1 --pretty=format:"%H|%s|%ci" ${branch}`,
5✔
148
    input: undefined,
5✔
149
  })
5✔
150

151
  const [sha, message, dateString] = stdout.trim().split("|")
5✔
152

153
  return { sha, message, date: new Date(dateString) }
25✔
154
}
2✔
155

156
/**
157
 * Makes sure that we have a local branch that has all of the commits that the remote branch has.
158
 *
159
 * There are a lot of commands here, just to get a local branch of a remote branch. After many attempts to simplify this, we would hit many different errors.
160
 * I believe that complexity comes because we run this tool on a CI server where the git config might be different.
161
 * Running all of these commands and running each command by itself (example: not running `git checkout -b` to try and combine creating a branch and checking it out)
162
 * have given the most consistent results.
163
 */
164
const createLocalBranchFromRemote = async (
2✔
165
  { exec, branch }: { exec: Exec; branch: string },
2✔
166
): Promise<void> => {
167
  const currentBranchName = (await exec.run({
4✔
168
    command: `git branch --show-current`,
4✔
169
    input: undefined,
4✔
170
  })).stdout.trim()
4✔
171

172
  // Perform a fetch, otherwise you might get errors about origin branch not being found.
173
  await exec.run({
5✔
174
    command: `git fetch origin`,
5✔
175
    input: undefined,
5✔
176
  })
5✔
177

178
  // Create a local branch that tracks the remote branch.
179
  await exec.run({
5✔
180
    command: `git branch --track ${branch} origin/${branch}`,
5✔
181
    input: undefined,
5✔
182
  })
5✔
183

184
  // Checkout the branch so we can pull it.
185
  await exec.run({
5✔
186
    command: `git checkout ${branch}`,
5✔
187
    input: undefined,
5✔
188
  })
5✔
189

190
  // Pull the branch from the remote.
191
  // Adding --no-rebase to avoid an error that could happen when you run pull.
192
  // The error is: You have divergent branches and need to specify how to reconcile them.
193
  await exec.run({
5✔
194
    command: `git pull --no-rebase origin ${branch}`,
5✔
195
    input: undefined,
5✔
196
  })
5✔
197

198
  // Switch back to the branch we were on before.
199
  await exec.run({
5✔
200
    command: `git checkout ${currentBranchName}`,
5✔
201
    input: undefined,
5✔
202
  })
5✔
203
}
2✔
204

205
export const git: Git = {
2✔
206
  checkoutBranch,
2✔
207
  merge,
2✔
208
  pull,
2✔
209
  setUser,
2✔
210
  squash,
2✔
211
  rebase,
2✔
212
  getLatestCommitsSince,
2✔
213
  getLatestCommitOnBranch,
2✔
214
  createLocalBranchFromRemote,
2✔
215
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc