Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multibranch support #129

Merged
merged 1 commit into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,17 @@ fall back to taking the filesystem timestamp into account.
`https://www.transifex.com/myorganization/myproject/new_feature--myresource`
resource.

> Note: Starting from version 1.5.0 resources created using the `--branch` flag,
will have an enhanced functionality in transifex and will be able to automatically
be merged into their bases. Resources created using the `--branch` prior to this
version, need to be pushed again in order for the new functionality to be available..

```sh
→ tx push --branch 'new_feature' --base '' myproject.myresource
```

- `--base`: Define the base branch when pushing a branch.

- `--skip`: Normally, if an upload fails, the client will abort. This may not
be desirable if most uploads are expected to succeed. For example, the reason
of the failed upload may be a syntax error in _one_ of the language files. If
Expand Down Expand Up @@ -781,7 +792,16 @@ tx delete project_slug.\*
If you supply an empty string as the branch (`--branch ''`), then the client
will attempt to figure out the currently active branch in the local git repository.

### Merging Resource
The tx merge command lets you merge a branch resource with its base resource (applies only to resources created with the `--branch` flag)

To merge a resource to its base resource, use the following command:
```
tx merge --branch branch_name project_slug.resource_slug
```
**Other flags:**
- `--conflict-resolution`: Set the conflict resolution strategy. Acceptable options are `USE_HEAD` (changes in the HEAD resource will be used) and `USE_BASE` (changes in the BASE resource will be used)
- `--force`: In case you want to proceed with the merge even if the source strings are diverged, use the `-f/--force` flag.

### Getting the local status of the project
The status command displays the existing configuration in a human readable format. It lists all resources that have been initialized under the local repo/directory and all their associated translation files:
Expand Down
104 changes: 104 additions & 0 deletions cmd/tx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,103 @@ func Main() {
return nil
},
},
{
Name: "merge",
Usage: "tx merge [options] [resource_id]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "branch",
Usage: "Merge specific branch (omit " +
"if it can be determined)",
Value: "",
},
&cli.StringFlag{
Name: "conflict-resolution",
Usage: "Conflict resolution to use for unresolved conflicts",
Value: "USE_BASE",
},
&cli.BoolFlag{
Name: "force",
Usage: "Force merge even if sources are diverged",
Aliases: []string{"f"},
Value: false,
},
&cli.BoolFlag{
Name: "skip",
Usage: "Whether to skip on errors",
},
&cli.BoolFlag{
Name: "silent",
Usage: "Whether to reduce verbosity of the output",
},
},
Action: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return cli.Exit(errorColor("Please provide one resource"), 1)
}

resourceId := c.Args().First()
cfg, err := config.LoadFromPaths(
c.String("root-config"),
c.String("config"),
)
if err != nil {
return cli.Exit(
errorColor(
"Error loading configuration: %s",
err,
),
1,
)
}
hostname, token, err := txlib.GetHostAndToken(
&cfg, c.String("hostname"), c.String("token"),
)
if err != nil {
return cli.Exit(
errorColor(
"Error getting API token: %s",
err,
),
1,
)
}

client, err := txlib.GetClient(c.String("cacert"))
if err != nil {
return cli.Exit(
errorColor(
"Error getting HTTP client configuration: %s",
err,
),
1,
)
}

api := jsonapi.Connection{
Host: hostname,
Token: token,
Client: client,
Headers: map[string]string{
"Integration": "txclient",
},
}

args := txlib.MergeCommandArguments{
ResourceId: resourceId,
Branch: c.String("branch"),
ConflictResolution: c.String("conflict-resolution"),
Force: c.Bool("force"),
Skip: c.Bool("skip"),
Silent: c.Bool("silent"),
}
err = txlib.MergeCommand(&cfg, api, args)
if err != nil {
return cli.Exit(err, 1)
}
return nil
},
},
{
Name: "push",
Usage: "tx push [options] [resource_id...]",
Expand Down Expand Up @@ -146,6 +243,12 @@ func Main() {
"determined)",
Value: "-1",
},
&cli.StringFlag{
Name: "base",
Usage: "Push current branch with a specific base branch. " +
"If omitted the main resource will be used as base",
Value: "-1",
},
&cli.IntFlag{
Name: "workers",
Usage: "How many parallel workers to use",
Expand Down Expand Up @@ -227,6 +330,7 @@ func Main() {
ResourceIds: resourceIds,
UseGitTimestamps: c.Bool("use-git-timestamps"),
Branch: c.String("branch"),
Base: c.String("base"),
All: c.Bool("all"),
Workers: c.Int("workers"),
Silent: c.Bool("silent"),
Expand Down
3 changes: 2 additions & 1 deletion internal/txlib/config/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ func nameToSlugsForMigrate(in string) (string, string, string, error) {

/*
Name Return the name of a resource as it appears in the configuration file. The
format is the same as the ID of the resource in APIv3 */
format is the same as the ID of the resource in APIv3
*/
func (localCfg *Resource) Name() string {
var result string
if localCfg.OrganizationSlug != "" {
Expand Down
130 changes: 130 additions & 0 deletions internal/txlib/merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package txlib

import (
"errors"
"fmt"
"strings"
"time"

"github.com/transifex/cli/internal/txlib/config"
"github.com/transifex/cli/pkg/jsonapi"
"github.com/transifex/cli/pkg/txapi"
"github.com/transifex/cli/pkg/worker_pool"
)

type MergeCommandArguments struct {
ResourceId string
Branch string
ConflictResolution string
Force bool
Skip bool
Silent bool
}

func MergeCommand(
cfg *config.Config,
api jsonapi.Connection,
args MergeCommandArguments,
) error {
args.Branch = figureOutBranch(args.Branch)

cfgResources, err := figureOutResources([]string{args.ResourceId}, cfg)
if err != nil {
return err
}

applyBranchToResources(cfgResources, args.Branch)

cfgResource := cfgResources[0]

err = mergeResource(&api, cfgResource, args)

return err
}

func mergeResource(
api *jsonapi.Connection, cfgResource *config.Resource, args MergeCommandArguments,
) error {
isValidPolicy := isValidResolutionPolicy(args.ConflictResolution)
if !isValidPolicy {
return fmt.Errorf("invalid resolution policy %s", args.ConflictResolution)
}

resourceId := fmt.Sprintf(
"o:%s:p:%s:r:%s",
cfgResource.OrganizationSlug,
cfgResource.ProjectSlug,
cfgResource.ResourceSlug,
)

// Get Resource from Server
resource, err := txapi.GetResourceById(api, resourceId)
if err != nil {
return fmt.Errorf("error getting resource '%s - %s - %s'",
cfgResource.OrganizationSlug,
cfgResource.ProjectSlug,
cfgResource.ResourceSlug)
}

if resource == nil {
return fmt.Errorf("resource not found '%s - %s - %s'",
cfgResource.OrganizationSlug,
cfgResource.ProjectSlug,
cfgResource.ResourceSlug)
}

var merge *jsonapi.Resource
merge, err = txapi.CreateAsyncResourceMerge(api, resource, args.ConflictResolution, args.Force)
if err != nil {
return err
}

pool := worker_pool.New(1, 1, args.Silent)
pool.Add(&MergeResourcePollTask{merge, args})
pool.Start()
<-pool.Wait()
if pool.IsAborted {
return errors.New("Aborted")
}

return nil
}

type MergeResourcePollTask struct {
merge *jsonapi.Resource
args MergeCommandArguments
}

func (task *MergeResourcePollTask) Run(send func(string), abort func()) {
merge := task.merge
args := task.args

parts := strings.Split(merge.Relationships["base"].DataSingular.Id, ":")
sendMessage := func(body string, force bool) {
if args.Silent && !force {
return
}
send(fmt.Sprintf(
"%s.%s - %s", parts[3], parts[5], body,
))
}

err := handleThrottling(
func() error {
return txapi.PollResourceMerge(
merge,
time.Second,
)
},
"Polling merge task status",
func(msg string) { sendMessage(msg, false) },
)
if err != nil {
sendMessage(err.Error(), true)
if !args.Skip {
abort()
}
return
}
sendMessage("Done", false)
}
Loading