diff --git a/spec/remote.spec.js b/spec/remote.spec.js index 5ce78013..c44bf44c 100644 --- a/spec/remote.spec.js +++ b/spec/remote.spec.js @@ -240,5 +240,19 @@ describe('Git Remotes', function() { ); }); + it('sets remote tracking', function() { + expectTreeAsync( + 'git clone; git branch foo; git branch -u o/master foo', + '{"branches":{"master":{"target":"C1","id":"master","remoteTrackingBranchID":"o/master"},"o/master":{"target":"C1","id":"o/master","remoteTrackingBranchID":null},"foo":{"target":"C1","id":"foo","remoteTrackingBranchID":"o/master"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"}},"HEAD":{"target":"master","id":"HEAD"},"originTree":{"branches":{"master":{"target":"C1","id":"master","remoteTrackingBranchID":null}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"}},"HEAD":{"target":"master","id":"HEAD"}}}' + ); + }); + + it('sets remote tracking on current branch if not specified', function() { + expectTreeAsync( + 'git clone; git checkout -b foo; git branch -u o/master', + '{"branches":{"master":{"target":"C1","id":"master","remoteTrackingBranchID":"o/master"},"o/master":{"target":"C1","id":"o/master","remoteTrackingBranchID":null},"foo":{"target":"C1","id":"foo","remoteTrackingBranchID":"o/master"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"}},"HEAD":{"target":"foo","id":"HEAD"},"originTree":{"branches":{"master":{"target":"C1","id":"master","remoteTrackingBranchID":null}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"}},"HEAD":{"target":"master","id":"HEAD"}}}' + ); + }); + }); diff --git a/src/js/git/commands.js b/src/js/git/commands.js index fd2b8b9f..ec2441a6 100644 --- a/src/js/git/commands.js +++ b/src/js/git/commands.js @@ -23,6 +23,32 @@ var validateBranchName = function(engine, name) { return engine.validateBranchName(name); }; +var assertIsBranch = function(engine, ref) { + assertIsRef(engine, ref); + var obj = engine.refs[ref]; + if (obj.get('type') !== 'branch') { + throw new GitError({ + msg: intl.todo( + ref + ' is not a branch' + ) + }); + } +}; + +var assertIsRemoteBranch = function(engine, ref) { + assertIsRef(engine, ref); + var obj = engine.refs[ref]; + + if (obj.get('type') !== 'branch' || + !obj.getIsRemote()) { + throw new GitError({ + msg: intl.todo( + ref + ' is not a remote branch' + ) + }); + } +}; + var assertOriginSpecified = function(generalArgs) { if (generalArgs[0] !== 'origin') { throw new GitError({ @@ -298,6 +324,7 @@ var commandConfig = { '-f', '-a', '-r', + '-u', '--contains' ], execute: function(engine, command) { @@ -316,6 +343,23 @@ var commandConfig = { return; } + if (commandOptions['-u']) { + command.acceptNoGeneralArgs(); + args = commandOptions['-u']; + command.validateArgBounds(args, 1, 2, '-u'); + var remoteBranch = crappyUnescape(args[0]); + var branch = args[1] || engine.getOneBeforeCommit('HEAD').get('id'); + + // some assertions, both of these have to exist first + assertIsRemoteBranch(engine, remoteBranch); + assertIsBranch(engine, branch); + engine.setLocalToTrackRemote( + engine.refs[branch], + engine.refs[remoteBranch] + ); + return; + } + if (commandOptions['--contains']) { args = commandOptions['--contains']; command.validateArgBounds(args, 1, 1, '--contains'); diff --git a/src/js/views/gitDemonstrationView.js b/src/js/views/gitDemonstrationView.js index d20569a9..807c8bea 100644 --- a/src/js/views/gitDemonstrationView.js +++ b/src/js/views/gitDemonstrationView.js @@ -171,7 +171,6 @@ var GitDemonstrationView = ContainedBase.extend({ _.each(commands, function(command, index) { chainPromise = chainPromise.then(_.bind(function() { var myDefer = Q.defer(); - console.log('dispatching', command); this.mainVis.gitEngine.dispatch(command, myDefer); return myDefer.promise; }, this)); diff --git a/src/levels/index.js b/src/levels/index.js index 7be50cd0..f0fb0655 100644 --- a/src/levels/index.js +++ b/src/levels/index.js @@ -37,6 +37,7 @@ exports.levelSequences = { require('./remote/fetchRebase').level ], remoteAdvanced: [ + require('./remote/tracking').level, require('./remote/pushManyFeatures').level, require('./remote/mergeManyFeatures').level ] diff --git a/src/levels/remote/specify.js b/src/levels/remote/specify.js deleted file mode 100644 index 03db9875..00000000 --- a/src/levels/remote/specify.js +++ /dev/null @@ -1,82 +0,0 @@ -exports.level = { - "goalTreeString": "%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22master%22%2C%22remoteTrackingBranchID%22%3A%22o/master%22%2C%22localBranchesThatTrackThis%22%3Anull%7D%2C%22foo%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22foo%22%2C%22remoteTrackingBranchID%22%3A%22o/foo%22%2C%22localBranchesThatTrackThis%22%3Anull%7D%2C%22o/master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22o/master%22%2C%22remoteTrackingBranchID%22%3Anull%2C%22localBranchesThatTrackThis%22%3A%5B%22master%22%5D%7D%2C%22o/foo%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22o/foo%22%2C%22remoteTrackingBranchID%22%3Anull%2C%22localBranchesThatTrackThis%22%3A%5B%22foo%22%5D%7D%2C%22side%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22side%22%2C%22remoteTrackingBranchID%22%3Anull%2C%22localBranchesThatTrackThis%22%3Anull%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22side%22%2C%22id%22%3A%22HEAD%22%7D%2C%22originTree%22%3A%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22master%22%2C%22remoteTrackingBranchID%22%3Anull%2C%22localBranchesThatTrackThis%22%3Anull%7D%2C%22foo%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22foo%22%2C%22remoteTrackingBranchID%22%3Anull%2C%22localBranchesThatTrackThis%22%3Anull%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22foo%22%2C%22id%22%3A%22HEAD%22%7D%7D%7D", - "solutionCommand": "git pull origin foo --rebase;git push origin foo", - "startTree": "{\"branches\":{\"master\":{\"target\":\"C1\",\"id\":\"master\",\"remoteTrackingBranchID\":\"o/master\",\"localBranchesThatTrackThis\":null},\"foo\":{\"target\":\"C1\",\"id\":\"foo\",\"remoteTrackingBranchID\":\"o/foo\",\"localBranchesThatTrackThis\":null},\"o/master\":{\"target\":\"C1\",\"id\":\"o/master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":[\"master\"]},\"o/foo\":{\"target\":\"C1\",\"id\":\"o/foo\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":[\"foo\"]},\"side\":{\"target\":\"C3\",\"id\":\"side\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"}},\"HEAD\":{\"target\":\"side\",\"id\":\"HEAD\"},\"originTree\":{\"branches\":{\"master\":{\"target\":\"C1\",\"id\":\"master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null},\"foo\":{\"target\":\"C2\",\"id\":\"foo\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C2\":{\"parents\":[\"C1\"],\"id\":\"C2\"}},\"HEAD\":{\"target\":\"foo\",\"id\":\"HEAD\"}}}", - "name": { - "en_US": "Specifying Remote/Branch" - }, - "hint": { - "en_US": "Review the intro dialogs if you get stuck!" - }, - "startDialog": { - "en_US": { - "childViews": [ - { - "type": "ModalAlert", - "options": { - "markdowns": [ - "### Specifying `/`", - "", - "One thing that might have seemed \"magical\" is that git knew to rebase our `master` branch onto `o/master`. How did it what branch we wanted? What if we wanted to rebase onto another branch or upload our work to a different branch name?", - "", - "Git knows to rebase onto `o/master` because the `master` branch is set up to *track* `o/master`. So yes, this is another branch property you have to understand -- when you clone a repository, git sets up a the remote branches (like `o/master`) and branches that *track* those remote branches (for instance, `master`).", - "", - "This way when you push and pull, you can simply leave off any arguments and git knows where to put your work." - ] - } - }, - { - "type": "ModalAlert", - "options": { - "markdowns": [ - "However, you can actually specify the remote and the branch name on the remote if you wish to. The command takes the format:", - "", - "* `git push `", - "* `git pull `", - "", - "Let's see a demonstration" - ] - } - }, - { - "type": "GitDemonstrationView", - "options": { - "beforeMarkdowns": [ - "I can `pull` down changes onto a totally separate branch..." - ], - "afterMarkdowns": [ - "See! The `side` branch was updated with work from `o/master` while the `master` branch was not updated at all." - ], - "command": "git checkout side; git pull origin master", - "beforeCommand": "git clone; git fakeTeamwork; git branch side" - } - }, - { - "type": "GitDemonstrationView", - "options": { - "beforeMarkdowns": [ - "I can also push to a different branch as well..." - ], - "afterMarkdowns": [ - "Here we pushed commits *from* the `side` branch onto the `master` branch on the remote. We did this by specifying the destination of our push.", - "", - "Notice how `o/master` is updated but *not* the `master` branch!" - ], - "command": "git push origin master", - "beforeCommand": "git clone; git checkout -b side; git commit" - } - }, - { - "type": "ModalAlert", - "options": { - "markdowns": [ - "Ok, this level is a bit tricky. Let's pull down work from the `foo` branch on remote, rebase our work on top of that, and publish that work back.", - "", - "Let's do all of this while *not* on the `foo` branch though, just because we can!" - ] - } - } - ] - } - } -}; diff --git a/src/levels/remote/tracking.js b/src/levels/remote/tracking.js new file mode 100644 index 00000000..e870e229 --- /dev/null +++ b/src/levels/remote/tracking.js @@ -0,0 +1,129 @@ +exports.level = { + "goalTreeString": "%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22master%22%2C%22remoteTrackingBranchID%22%3A%22o/master%22%7D%2C%22o/master%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22o/master%22%2C%22remoteTrackingBranchID%22%3Anull%7D%2C%22side%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22side%22%2C%22remoteTrackingBranchID%22%3A%22o/master%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22side%22%2C%22id%22%3A%22HEAD%22%7D%2C%22originTree%22%3A%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22master%22%2C%22remoteTrackingBranchID%22%3Anull%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22master%22%2C%22id%22%3A%22HEAD%22%7D%7D%7D", + "solutionCommand": "git checkout -b side o/master;git commit;git pull --rebase;git push", + "startTree": "{\"branches\":{\"master\":{\"target\":\"C1\",\"id\":\"master\",\"remoteTrackingBranchID\":\"o/master\"},\"o/master\":{\"target\":\"C1\",\"id\":\"o/master\",\"remoteTrackingBranchID\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"},\"originTree\":{\"branches\":{\"master\":{\"target\":\"C2\",\"id\":\"master\",\"remoteTrackingBranchID\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C2\":{\"parents\":[\"C1\"],\"id\":\"C2\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"}}}", + "name": { + "en_US": "Remote Tracking" + }, + "hint": { + "en_US": "Remember there are two ways to set remote tracking!" + }, + "startDialog": { + "en_US": { + "childViews": [ + { + "type": "ModalAlert", + "options": { + "markdowns": [ + "### Remote-Tracking branches", + "", + "One thing that might have seemed \"magical\" about the last few lessons is that git knew the `master` branch was related to `o/master`. Sure these branches have similar names and it might make logical sense to connect the `master` branch on the remote to the local `master` branch, but this connection is demonstrated clearly in two scenarios:", + "", + "* During a pull operation, commits are downloaded onto `o/master` and then *merged* into the `master` branch. The implied target of the merge is determined from this connection.", + "* During a push operation, work from the `master` branch was pushed onto the remote's `master` branch (which was then represented by `o/master` locally). The *destination* of the push is determined from the connection between `master` and `o/master`.", + "" + ] + } + }, + { + "type": "ModalAlert", + "options": { + "markdowns": [ + "## Remote tracking", + "", + "Long story short, this connection between `master` and `o/master` is explained simply by the \"remote tracking\" property of branches. The `master` branch is set to track `o/master` -- this means there is an implied merge target and implied push destination for the `master` branch.", + "", + "You may be wondering how this property got set on the `master` branch when you didn't run any commands to specify it. Well, when you clone a repository with git, this property is actually set for you automatically. ", + "", + "During a clone, git creates a remote branch for every branch on the remote (aka branches like `o/master`) and then, for each remote branch, creates a local branch to *track* that remote branch (aka `master`). Thats why you may have seen the following command output:", + "", + " local branch \"master\" set to track remote branch \"o/master\"", + "", + "When running `git clone`." + ] + } + }, + { + "type": "ModalAlert", + "options": { + "markdowns": [ + "### Can I specify this myself?", + "", + "Yes you can! You can make any arbitrary branch track `o/master`, and if you do so, that branch will have the same implied push destination and merge target as `master`. This means you can run `git push` on a branch named `totallyNotMaster` and have your work pushed to the `master` branch on the remote!", + "", + "There are two ways to set this property. The first is to checkout a new branch by using a remote branch as the specified ref. Running", + "", + "`git checkout -b totallyNotMaster o/master`", + "", + "Creates a new branch named `totallyNotMaster` and sets it to track `o/master`." + ] + } + }, + { + "type": "GitDemonstrationView", + "options": { + "beforeMarkdowns": [ + "Enough talking, let's see a demonstration! We will checkout a new branch named `foo` and set it to track `master` on the remote." + ], + "afterMarkdowns": [ + "As you can see, we used the implied merge target of `o/master` to update the `foo` branch. Note how master doesn't get updated!!" + ], + "command": "git checkout -b foo o/master; git pull", + "beforeCommand": "git clone; git fakeTeamwork" + } + }, + { + "type": "GitDemonstrationView", + "options": { + "beforeMarkdowns": [ + "This also applies for git push" + ], + "afterMarkdowns": [ + "Boom. We pushed our work to the `master` on the remote even though our branch was named something totally different" + ], + "command": "git checkout -b foo o/master; git commit; git push", + "beforeCommand": "git clone" + } + }, + { + "type": "ModalAlert", + "options": { + "markdowns": [ + "### Way #2", + "", + "Another way to set remote tracking on a branch is to simply use the `git branch -u` option. Running", + "", + "`git branch -u o/master foo`", + "", + "will set the `foo` branch to track `o/master`. If `foo` is currently checked out you can even leave it off:", + "", + "`git branch -u o/master`", + "" + ] + } + }, + { + "type": "GitDemonstrationView", + "options": { + "beforeMarkdowns": [ + "Let's see this other way of specifying remote tracking real quick..." + ], + "afterMarkdowns": [ + "Same as before, just a more explicit command. Sweet!" + ], + "command": "git branch -u o/master foo; git commit; git push", + "beforeCommand": "git clone; git checkout -b foo" + } + }, + { + "type": "ModalAlert", + "options": { + "markdowns": [ + "Ok! For this level let's push work onto the `master` branch on remote while *not* checked out on `master` locally. I'll let you figure out the rest since this is the advanced course :P" + ] + } + } + ] + } + } +}; diff --git a/src/style/main.css b/src/style/main.css index 254cec35..70d520e3 100644 --- a/src/style/main.css +++ b/src/style/main.css @@ -38,6 +38,8 @@ div.modalTerminal a { font-weight: bold; } + +div.modalTerminal li, div.modalTerminal p { line-height: 24px; margin: 1.5em 0; diff --git a/todo.txt b/todo.txt index d51939e9..23d4869c 100644 --- a/todo.txt +++ b/todo.txt @@ -5,12 +5,13 @@ Big Things Origin things: ~~~~~~~~~~~~~~~~~~~~~~~~~~ [ ] polish visual layout?? needed? -[ ] work on TABBED levels layout Medium things: ~~~~~~~~~~~~~~~~~~~~~~~~~~~ [ ] figure out what to do with instant commands (and parse waterfall and the like) [ ] disable git commands on hg levels (and vice versa) +[ ] make helper bar clickable with goal vis floating +[ ] make show solution easier Argument things: @@ -39,6 +40,7 @@ Ideas for cleaning Done things: (I only started this on Dec 17th 2012 to get a better sense of what was done) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +[x] work on TABBED levels layout [x] EASY -- make colors the same between remote branches and their remote counterparts [x] fix undo not syncing the remote tracking [x] get clone as a before command working in demonstration views (related to test infra as well)