Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

knotserver/git: write change-id headers into git objects

after applying a patch series, write the change-id headers into the git
commit objects itself.

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
04f647ee 8ec3dbea

+153 -40
+151 -38
knotserver/git/merge.go
··· 4 4 "bytes" 5 5 "crypto/sha256" 6 6 "fmt" 7 + "log" 7 8 "os" 8 9 "os/exec" 9 10 "regexp" ··· 13 12 "github.com/dgraph-io/ristretto" 14 13 "github.com/go-git/go-git/v5" 15 14 "github.com/go-git/go-git/v5/plumbing" 15 + "tangled.org/core/patchutil" 16 + "tangled.org/core/types" 16 17 ) 17 18 18 19 type MergeCheckCache struct { ··· 165 162 return nil 166 163 } 167 164 168 - func (g *GitRepo) applyPatch(tmpDir, patchFile string, opts MergeOptions) error { 165 + func (g *GitRepo) applyPatch(patchData, patchFile string, opts MergeOptions) error { 169 166 var stderr bytes.Buffer 170 167 var cmd *exec.Cmd 171 168 172 169 // configure default git user before merge 173 - exec.Command("git", "-C", tmpDir, "config", "user.name", opts.CommitterName).Run() 174 - exec.Command("git", "-C", tmpDir, "config", "user.email", opts.CommitterEmail).Run() 175 - exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run() 170 + exec.Command("git", "-C", g.path, "config", "user.name", opts.CommitterName).Run() 171 + exec.Command("git", "-C", g.path, "config", "user.email", opts.CommitterEmail).Run() 172 + exec.Command("git", "-C", g.path, "config", "advice.mergeConflict", "false").Run() 176 173 177 174 // if patch is a format-patch, apply using 'git am' 178 175 if opts.FormatPatch { 179 - cmd = exec.Command("git", "-C", tmpDir, "am", patchFile) 180 - } else { 181 - // else, apply using 'git apply' and commit it manually 182 - applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile) 183 - applyCmd.Stderr = &stderr 184 - if err := applyCmd.Run(); err != nil { 185 - return fmt.Errorf("patch application failed: %s", stderr.String()) 186 - } 187 - 188 - stageCmd := exec.Command("git", "-C", tmpDir, "add", ".") 189 - if err := stageCmd.Run(); err != nil { 190 - return fmt.Errorf("failed to stage changes: %w", err) 191 - } 192 - 193 - commitArgs := []string{"-C", tmpDir, "commit"} 194 - 195 - // Set author if provided 196 - authorName := opts.AuthorName 197 - authorEmail := opts.AuthorEmail 198 - 199 - if authorName != "" && authorEmail != "" { 200 - commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail)) 201 - } 202 - // else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables 203 - 204 - commitArgs = append(commitArgs, "-m", opts.CommitMessage) 205 - 206 - if opts.CommitBody != "" { 207 - commitArgs = append(commitArgs, "-m", opts.CommitBody) 208 - } 209 - 210 - cmd = exec.Command("git", commitArgs...) 176 + return g.applyMailbox(patchData) 211 177 } 178 + 179 + // else, apply using 'git apply' and commit it manually 180 + applyCmd := exec.Command("git", "-C", g.path, "apply", patchFile) 181 + applyCmd.Stderr = &stderr 182 + if err := applyCmd.Run(); err != nil { 183 + return fmt.Errorf("patch application failed: %s", stderr.String()) 184 + } 185 + 186 + stageCmd := exec.Command("git", "-C", g.path, "add", ".") 187 + if err := stageCmd.Run(); err != nil { 188 + return fmt.Errorf("failed to stage changes: %w", err) 189 + } 190 + 191 + commitArgs := []string{"-C", g.path, "commit"} 192 + 193 + // Set author if provided 194 + authorName := opts.AuthorName 195 + authorEmail := opts.AuthorEmail 196 + 197 + if authorName != "" && authorEmail != "" { 198 + commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail)) 199 + } 200 + // else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables 201 + 202 + commitArgs = append(commitArgs, "-m", opts.CommitMessage) 203 + 204 + if opts.CommitBody != "" { 205 + commitArgs = append(commitArgs, "-m", opts.CommitBody) 206 + } 207 + 208 + cmd = exec.Command("git", commitArgs...) 212 209 213 210 cmd.Stderr = &stderr 214 211 ··· 219 216 return nil 220 217 } 221 218 222 - func (g *GitRepo) MergeCheck(patchData []byte, targetBranch string) error { 219 + func (g *GitRepo) applyMailbox(patchData string) error { 220 + fps, err := patchutil.ExtractPatches(patchData) 221 + if err != nil { 222 + return fmt.Errorf("failed to extract patches: %w", err) 223 + } 224 + 225 + // apply each patch one by one 226 + // update the newly created commit object to add the change-id header 227 + total := len(fps) 228 + for i, p := range fps { 229 + newCommit, err := g.applySingleMailbox(p) 230 + if err != nil { 231 + return err 232 + } 233 + 234 + log.Printf("applying mailbox patch %d/%d: committed %s\n", i+1, total, newCommit.String()) 235 + } 236 + 237 + return nil 238 + } 239 + 240 + func (g *GitRepo) applySingleMailbox(singlePatch types.FormatPatch) (plumbing.Hash, error) { 241 + tmpPatch, err := g.createTempFileWithPatch(singlePatch.Raw) 242 + if err != nil { 243 + return plumbing.ZeroHash, fmt.Errorf("failed to create temporary patch file for singluar mailbox patch: %w", err) 244 + } 245 + 246 + var stderr bytes.Buffer 247 + cmd := exec.Command("git", "-C", g.path, "am", tmpPatch) 248 + cmd.Stderr = &stderr 249 + 250 + head, err := g.r.Head() 251 + if err != nil { 252 + return plumbing.ZeroHash, err 253 + } 254 + log.Println("head before apply", head.Hash().String()) 255 + 256 + if err := cmd.Run(); err != nil { 257 + return plumbing.ZeroHash, fmt.Errorf("patch application failed: %s", stderr.String()) 258 + } 259 + 260 + if err := g.Refresh(); err != nil { 261 + return plumbing.ZeroHash, fmt.Errorf("failed to refresh repository state: %w", err) 262 + } 263 + 264 + head, err = g.r.Head() 265 + if err != nil { 266 + return plumbing.ZeroHash, err 267 + } 268 + log.Println("head after apply", head.Hash().String()) 269 + 270 + newHash := head.Hash() 271 + if changeId, err := singlePatch.ChangeId(); err != nil { 272 + // no change ID 273 + } else if updatedHash, err := g.setChangeId(head.Hash(), changeId); err != nil { 274 + return plumbing.ZeroHash, err 275 + } else { 276 + newHash = updatedHash 277 + } 278 + 279 + return newHash, nil 280 + } 281 + 282 + func (g *GitRepo) setChangeId(hash plumbing.Hash, changeId string) (plumbing.Hash, error) { 283 + log.Printf("updating change ID of %s to %s\n", hash.String(), changeId) 284 + obj, err := g.r.CommitObject(hash) 285 + if err != nil { 286 + return plumbing.ZeroHash, fmt.Errorf("failed to get commit object for hash %s: %w", hash.String(), err) 287 + } 288 + 289 + // write the change-id header 290 + obj.ExtraHeaders["change-id"] = []byte(changeId) 291 + 292 + // create a new object 293 + dest := g.r.Storer.NewEncodedObject() 294 + if err := obj.Encode(dest); err != nil { 295 + return plumbing.ZeroHash, fmt.Errorf("failed to create new object: %w", err) 296 + } 297 + 298 + // store the new object 299 + newHash, err := g.r.Storer.SetEncodedObject(dest) 300 + if err != nil { 301 + return plumbing.ZeroHash, fmt.Errorf("failed to store new object: %w", err) 302 + } 303 + 304 + log.Printf("hash changed from %s to %s\n", obj.Hash.String(), newHash.String()) 305 + 306 + // find the branch that HEAD is pointing to 307 + ref, err := g.r.Head() 308 + if err != nil { 309 + return plumbing.ZeroHash, fmt.Errorf("failed to fetch HEAD: %w", err) 310 + } 311 + 312 + // and update that branch to point to new commit 313 + if ref.Name().IsBranch() { 314 + err = g.r.Storer.SetReference(plumbing.NewHashReference(ref.Name(), newHash)) 315 + if err != nil { 316 + return plumbing.ZeroHash, fmt.Errorf("failed to update HEAD: %w", err) 317 + } 318 + } 319 + 320 + // new hash of commit 321 + return newHash, nil 322 + } 323 + 324 + func (g *GitRepo) MergeCheck(patchData string, targetBranch string) error { 223 325 if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok { 224 326 return val 225 327 } ··· 371 263 } 372 264 defer os.RemoveAll(tmpDir) 373 265 374 - if err := g.applyPatch(tmpDir, patchFile, opts); err != nil { 266 + tmpRepo, err := PlainOpen(tmpDir) 267 + if err != nil { 268 + return err 269 + } 270 + 271 + if err := tmpRepo.applyPatch(patchData, patchFile, opts); err != nil { 375 272 return err 376 273 } 377 274
+1 -1
knotserver/xrpc/merge.go
··· 85 85 mo.CommitterEmail = x.Config.Git.UserEmail 86 86 mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 87 87 88 - err = gr.MergeWithOptions([]byte(data.Patch), data.Branch, mo) 88 + err = gr.MergeWithOptions(data.Patch, data.Branch, mo) 89 89 if err != nil { 90 90 var mergeErr *git.ErrMerge 91 91 if errors.As(err, &mergeErr) {
+1 -1
knotserver/xrpc/merge_check.go
··· 51 51 return 52 52 } 53 53 54 - err = gr.MergeCheck([]byte(data.Patch), data.Branch) 54 + err = gr.MergeCheck(data.Patch, data.Branch) 55 55 56 56 response := tangled.RepoMergeCheck_Output{ 57 57 Is_conflicted: false,