Skip to content

Commit

Permalink
Merge pull request #784 from WebCabin/external-merge-tool
Browse files Browse the repository at this point in the history
External merge tool support
  • Loading branch information
jung-kim authored Sep 8, 2016
2 parents 66525f4 + dcad8af commit 278e194
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/) and
[Keep a changlelog's changelog standard](http://keepachangelog.com/)

### Added
- New configuration option `mergeTool` allows you to assign a custom external merge tool for conflict resolution [783](https://github.com/FredrikNoren/ungit/issues/783)

## [Unreleased](https://github.com/FredrikNoren/ungit/compare/v0.10.3...master)
- Fix for favorites linking in case rootPath is used @sebastianmay [#609](https://github.com/FredrikNoren/ungit/issues/609) and image diffing

Expand Down
69 changes: 69 additions & 0 deletions MERGETOOL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
If you have your own merge tool that you would like to use, such as Kaleidoscope or p4merge, you can configure ungit to use it by following these steps:


1. Configuring git
------------------

The first step is to configure git so that it knows how to invoke your merge tool. In your home directory, open (or create) the git configuration file .gitconfig. In this file, you will want to add information about your merge tool, it should look something like this:

```ini
[mergetool "extMerge"]
cmd = extMergeTool "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
trustExitCode = false
```

* `"extMergeTool"` is the merge tool you are invoking. This assumes your merge tool was installed and the command is recognized by your system. You may also replace this with the path to your merge tool directly.
* For best results, refer to the documentation of your merge tool, as it may require different command arguments.
* The name `"extMerge"` can be whatever you want. I recommend that it not contain spaces or special symbols, as it may interfere when used as a command argument.
* `"trustExitCode"` depends on the merge tool you are using. If `true`, git will use the return code of your merge tool to determine whether the conflict has been resolved, otherwise it will use the timestamp of the file to determine this (meaning if your merge tool saved over the file, it will assume it has been resolved).
* Additionally, you can also provide the following if you want to identify your merge tool as the default:

```ini
[merge]
tool = extMerge
```

If you wish to test your configuration, open a console in a git repo that is currently waiting for conflict resolution and type the following command:
`> git mergetool --tool extMerge`
This should invoke your merge tool and cycle through each conflicted file.


2. Configuring ungit
--------------------

Add the `"mergeTool"` option to your ungit configuration file (.ungitrc). Set this value to `true` if you have configured a default merge tool with git, otherwise use the name of the merge tool you have configured (for example `"extMerge"`). It should look something like this:

```json
{
"mergeTool": "extMerge"
}
```

3. Use ungit's interface
------------------------

Start ungit and navigate to a repo with conflicted files. Now when you hover over the `Conflicts` label displayed on one of the conflicted files, it should expand and give you an option to `Launch Merge Tool`.

Once you have used your merge tool to resolve the conflicts, if ungit does not immediately recognize this, you may use the `Mark as Resolved` button to manually tell git that the file is now resolved.


4. Known Issues and Troubleshooting
-----------------------------------

* In some cases, your merge tool may take a few seconds to launch. Pressing the launch button multiple times will cause your merge tool to launch that many copies.
* Some merge tools (like `vimdiff`) are terminal-only tools and will not work with ungit. Your merge tool must work in a windowed environment. See the suggested merge tool section below.
* If for any reason git does not recognize that your merge tool has resolved the file, `trustExitCode` may need to be set to `false`.
* When your merge tool is launched, four auto-generated files will appear. If you have `trustExitCode` set to `false` and you cancel the merge tool, it may leave the generated files there. In this case, it is safe to manually remove them.


5. Merge Tool Suggestions
-------------------------
* Mac OS X:
* Meld: [meldmerge.org](http://meldmerge.org)
* Kaleidoscope: [www.kaleidoscopeapp.com](http://www.kaleidoscopeapp.com)
* Araxis Merge: [www.araxis.com](http://www.araxis.com)
* DeltaWalker: [www.deltopia.com](http://www.deltopia.com)
* Windows:
* Beyond Compare: [www.scootersoftware.com](http://www.scootersoftware.com)
* Araxis Merge: [www.araxis.com](http://www.araxis.com)
* P4Merge: [www.perforce.com](http://www.perforce.com)
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ Example of `~/.ungitrc` configuration file to change default port and enable bug

Ungit uses [rc](https://github.com/dominictarr/rc) for configuration, which in turn uses [yargs](https://github.com/yargs/yargs) for command line arguments. See corresponding documentations for more details.

External Merge Tools
--------------------
If you have your own merge tool that you would like to use, such as Kaleidoscope or p4merge, you can configure ungit to use it. See [MERGETOOL.md](MERGETOOL.md).

Plugins
-------
Plugins are installed by simply placing them in the Ungit plugin directory (`~/.ungit/plugins` by default), and then restarting Ungit.
Expand Down
2 changes: 1 addition & 1 deletion components/staging/staging.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
<span class="deleted" data-bind="visible: removed">Removed</span>
<span class="additions" data-bind="text: additions"></span>
<span class="deletions" data-bind="text: deletions"></span>
<span class="conflict" data-bind="visible: conflict, click: resolveConflict"><span class="explanation">Resolve&nbsp;</span>Conflict</span>
<span class="conflict" data-bind="visible: conflict"><span class="temporary">Conflicts</span><span class="launchmergetool explanation" data-bind="visible: mergeTool, click: launchMergeTool">Launch Merge Tool</span><span class="markresolved explanation" data-bind="click: resolveConflict">Mark as Resolved</span></span>
<span class="patch bootstrap-tooltip" data-bind="visible: isShowPatch(), click: patchClick"
data-toggle="tooltip" data-placement="top" data-ta-clickable="patch-file" title="Patch changes">Patch</span>
<span class="ignore bootstrap-tooltip" data-ta-clickable="ignore-file" data-bind="click: ignoreFile"
Expand Down
7 changes: 7 additions & 0 deletions components/staging/staging.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var filesToDisplayIncrmentBy = 50;
var filesToDisplayLimit = filesToDisplayIncrmentBy;
// when discard button is clicked and disable discard warning is selected, for next 5 minutes disable discard warnings
var muteGraceTimeDuration = 60 * 1000 * 5;
var mergeTool = ungit.config.mergeTool;

components.register('staging', function(args) {
return new StagingViewModel(args.server, args.repoPath);
Expand Down Expand Up @@ -325,6 +326,9 @@ var FileViewModel = function(staging, name, textDiffType, wordWrap) {
// and if diff is showing, display patch button
return !self.isNew() && !staging.inMerge() && !staging.inRebase() && self.fileType() === 'text' && self.isShowingDiffs();
});
this.mergeTool = ko.computed(function() {
return self.conflict() && mergeTool !== false;
});

this.editState.subscribe(function (value) {
if (value === 'none') {
Expand Down Expand Up @@ -394,6 +398,9 @@ FileViewModel.prototype.ignoreFile = function() {
FileViewModel.prototype.resolveConflict = function() {
this.server.post('/resolveconflicts', { path: this.staging.repoPath(), files: [this.name()] });
}
FileViewModel.prototype.launchMergeTool = function() {
this.server.post('/launchmergetool', { path: this.staging.repoPath(), file: this.name(), tool: mergeTool });
}
FileViewModel.prototype.toggleDiffs = function() {
if (this.renamed()) return; // do not show diffs for renames
if (this.isShowingDiffs()) {
Expand Down
31 changes: 30 additions & 1 deletion components/staging/staging.less
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
}
padding: 0.3em;

.new, .deleted, .conflict {
.new, .deleted, .conflict, .markresolved, .launchmergetool {
padding: 3px;
padding-left: 5px;
padding-right: 5px;
Expand All @@ -163,6 +163,20 @@
vertical-align: middle;
}
.conflict {
color: #DB12C0;
.explanation {
display: none;
}
&:hover {
.explanation {
display: inline;
}
.temporary {
display: none;
}
}
}
.markresolved {
color: #DB12C0;
cursor: pointer;
.explanation {
Expand All @@ -177,6 +191,21 @@
}
}
}
.launchmergetool {
color: #DB55FF;
cursor: pointer;
.explanation {
display: none;
}
&:hover {
background: #A477FF;
color: #000;
border-radius: 3px;
.explanation {
display: inline;
}
}
}
}
}
.btn-group {
Expand Down
7 changes: 6 additions & 1 deletion source/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ const defaultConfig = {

// number of nodes to load for each git.log call
numberOfNodesPerLoad: 25,

// Specifies a custom git merge tool to use when resolving conflicts. Your git configuration must be set up to use this!
// A true value will use the default tool while a string value will use the tool of that specified name.
mergeTool: false
};

// Works for now but should be moved to bin/ungit
Expand Down Expand Up @@ -176,7 +180,8 @@ let argv = yargs
.describe('lockConflictRetryCount', 'Allowed number of retry for git "index.lock" conflict')
.describe('autoCheckoutOnBranchCreate', 'Auto checkout the created branch on creation')
.describe('alwaysLoadActiveBranch', 'Always load with active checkout branch')
.describe('numberOfNodesPerLoad', 'number of nodes to load for each git.log call');
.describe('numberOfNodesPerLoad', 'number of nodes to load for each git.log call')
.describe('mergeTool', 'the git merge tool to use when resolving conflicts');

// If not triggered by test, then do strict option check
if (argv.$0.indexOf('mocha') === -1) {
Expand Down
9 changes: 9 additions & 0 deletions source/git-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,10 +424,19 @@ exports.registerApi = (env) => {
});

app.post(`${exports.pathPrefix}/resolveconflicts`, ensureAuthenticated, ensurePathExists, (req, res) => {
console.log('resolve conflicts');
jsonResultOrFailProm(res, gitPromise.resolveConflicts(req.body.path, req.body.files))
.then(emitWorkingTreeChanged.bind(null, req.body.path));
});

app.post(`${exports.pathPrefix}/launchmergetool`, ensureAuthenticated, ensurePathExists, (req, res) => {
const commands = ['mergetool', ...(typeof req.body.tool === 'string'? ['--tool ', req.body.tool]: []), '--no-prompt', req.body.file];
gitPromise(commands, req.body.path);
// Send immediate response, this is because merging may take a long time
// and there is no need to wait for it to finish.
res.json({});
});

app.get(`${exports.pathPrefix}/baserepopath`, ensureAuthenticated, ensurePathExists, (req, res) => {
const currentPath = path.resolve(path.join(req.query.path, '..'));
jsonResultOrFailProm(res, gitPromise(['rev-parse', '--show-toplevel'], currentPath)
Expand Down

0 comments on commit 278e194

Please sign in to comment.