 In the terminology of Mercurial, the revisions, the snapshots within a repository are usually known as change sets. So each time you check in your code from the working directory, you're creating a new change set. Each change set is uniquely identified by a change set ID, which is generated as a SHA-1 hash of the content of that revision of that change set. And a SHA-1 hash is 160 bits in length, and so represented as hex is 40 digits long. But because a 40 digit long hex number is pretty ugly, Mercurial will usually display these IDs abbreviated to their first 12 digits. Now, the purpose of these change set IDs is that you have a unique identifier for each change set, which is unique across all repositories. So when this change set gets sent around to different repositories, it can reliably be uniquely identified. As a convenience though, every change set in a repo is also known by a local revision number. These are numbers assigned to the change sets in the order in which they are added to the repository, starting from zero. Just be clear though that these numbers only uniquely identify a change set within that repository. So the change set with local revision number 8 in my repository is not going to necessarily be the same change set with local revision number 8 in any other repository. By convention in Mercurial, the local revision number and the change set ID of a single change set are displayed in this form, with the local revision number first followed by a colon, then the short form, the 12 digit form of the change set ID. Every change set in Mercurial has a parent, the only exception being the special null change set, which is an empty change set and the parent of the first commit. So here in this diagram we're representing a repository where we've made three commits. The first with the local revision number zero and a parent of null. The second with the local revision number of one and the parent of local revision zero. And the third having the local revision number two and local revision one is its parent. So we have three successive snapshots of our working directory. So the user of this repository first created their project. They initialized the repository to create it. They did a bit of work. They added some files into their project and then they made their first commit. Then they did some more work probably adding more files, editing their existing files and made a second commit. And then did more work, made more changes and made their third commit. A very important concept in Mercurial is that the working directory is sort of like a pseudo change set itself. It's a change set in waiting that we have yet to commit to the repository. And very importantly, the working directory has a current parent, meaning if we were to then commit the working directory, the new change set would have the same parent and the working directory's parent would be updated to be the new change set. So the working director's parent is automatically updated every time you make a new commit, but you can also manually change the parent of the working directory, which is something you will do when you want to create a new branch based on an older revision. So here for example, we've updated the working directory's parent back to the first change set and so now if we were to commit, this would create a change set whose parent is that first commit is localRevision0. What we've done effectively is create a new head and a new tip and the terminology of Mercurial, a head is a change set with no children and the tip is the newest head in that repo. It's the head with the highest local revision number. Before this last commit, the repo only had one head, revision2, and that also was the tip because it was the only head. When it comes time to merge two branches, we update the working directory to have two parents, not just one. In the working directory, we then go about the business of merging these two versions together into one and then we can commit our merge as a new change set which will have these two parents and as usual the working director itself is updated such that the new change set is its single parent. One of the most important concepts in Mercurial is that change sets are immutable, meaning once a change set is created it can't be modified and even more restrictive you can't even delete change sets. Once a change set is in a repository you don't get rid of it. The general reason for this is that the versions of the files in a change set are not necessarily stored in whole. Most of the time most files are stored as diffs based upon the previous version. The versions in the parent of that change set. And in fact in that parent there may not be full copies of the file either. There are likely just diffs. So when a particular version of a file is retrieved from the repository it is reconstructed from a bunch of diffs applied to the original version in the original commit. Though actually in some cases Mercurial will decide that in some later change set rather than storing a diff it makes more sense to sort that version of the file in whole for that change set. So what's stored in a change set is first a manifest of all the files, a list of all the files in that particular revision and then you'll have for some files in the manifest you'll have just a diff based upon some earlier version or you'll have a snapshot which is to say a whole copy of that particular version of that file. And then also in the change set of course is stored the parent ID or IDs if there's two which is the case when you're merging together two other change sets and then there are a few other pieces of media information like a time stamp of when the change set was created usually the name and email address of the committer, the person who made the change set and then also a commit message just a short textual description about that revision supplied by the person who committed it. The first two Mercurial commands we'll look at are init and clone. Init short for initialization is used to create a totally new repo whereas clone is used to create a repo which is a copy of some other existing repo that is a repo in which all the same change sets are present. As you'll see when we clone a repo the repo from which we are cloning is very often remote it's somewhere out there on the network. First though we'll demonstrate cloning a local repo a repo which resides in the local file system. So looking at the command line on my Unix system I have my user prompt here reading Brian at Ubuntu that's my username and then after the colon is the shell's current working directory and tilde is just short hand for my home directory so slash home slash brian. And for the first command here I write hg space init space foo hg is the name of the Mercurial binary the executable file located in my bin directory hg remember is the atomic symbol for Mercury and the commands called hg on Mercurial because hg is much more convenient to write. And the general pattern with the hg command is that the first argument is the name of the sub command we're invoking. Mercurial has a few dozen sub commands init and clone are just the first two we'll look at. So here when we write hg init foo foo is the argument to the init command in this case init is expecting you to specify a directory in which to initialize a new repository. So assuming I didn't already have a directory named foo hg init will create it and then in that directory it will create a sub directory called .hg and we demonstrate this by listing the content of the foo directory with ls-a foo. Recall that by convention in Unix files and directories beginning with a period are considered hidden directories and files meaning that the ls program doesn't normally list them. So we use the hyphen a option here which tells ls to list all the contents even the hidden files and directories. Now this .hg directory that is the actual repository that's where the chain sets and all the other information the repository needs gets stored. The directory foo itself is our working directory for that repository. So later inside foo we'll be adding files and committing them. So now having created this repository in our last command we clone it we clone it to a directory bar also in my home directory and as you can see once the clone operation finishes McAriel prints out two lines on the status of the new repository. So there's a bit of an asymmetry there when you use clone it prints out some status but when you use init it just does so silently without reporting anything. I guess because when you initialize a totally new repository it's just always just this empty .hg directory so there's never anything to report. To send chain sets from one repository another we have two commands push and pull. In a push you're sending chain sets from your repository to some other whereas in a pull you're bringing in chain sets from some other repository into your own. Pushes and pulls are probably most commonly done between repositories on separate systems over the network but it is also possible to push and pull between repositories on the same system. So for example with our two repositories foo and bar if I want to push from foo to bar I first change directory into the working directory of the foo repository because when you perform a push or pull the repository from which you are pushing or into which you are pulling that's not specified as an option on the command line it's taken from the current working directory of the McAriel process which is inherited from the shell process so that's why we changed the current working directory of our shell first. So once we're in the directory of the foo repository and we invoke hg push the only argument we provide is to specify as the path of the repository to which we are pushing and pull works the same way we specify the path to the repository from which we are pulling. Now the push and pull commands are smart in the sense that they will only copy the chain sets that need to be copied that is those chain sets which the destination repository does not already have and because we just cloned bar from foo before we invoked this push and pull well there are no change sets that one has that the other doesn't have actually both of these repositories currently don't have any change sets at all but the reason it says no change is found is because foo and bar currently have exactly the same set of change sets which just happens to be no change sets at all. Now it's important to understand that the idea with pushes and pulls is that we only exchange chain sets between repositories which are related meaning that one is a clone of the other either directly or indirectly if you have repository A which is cloned from B which is cloned from C or also related just indirectly. Now if you do have two unrelated repositories you can push and pull between them the problem is just that it just doesn't really make sense it's not something you generally want to do in fact it's something you want to avoid doing on accident because it means polluting a repo with all sorts of change sets not related to your project and in fact the way most people work with Mercurial it's common for a repo to push and pull exclusively with the repo from which it was cloned. So with this in mind when you clone a repository Mercurial creates a configuration file in the .hg directory called HGRC which stands for Mercurial Run Commands so for example in our bar repository which was cloned from the foo repository you'll see these two lines paths and underneath default equals slash home slash brian slash foo which is the path to the repository from which this repository was cloned and the significance of the default path is that if we invoke the push and pull commands without specifying any repo they will be assumed by default so I can invoke hg push and it pushes by default to slash home slash brian slash foo and likewise hg pull without any argument is pulling by default from slash home slash brian slash foo so you'll find most of the time when you invoke push and pull hg push or hg pull you don't specify any other repo so now we have the commands in it and clone for creating repos and then push and pull for exchanging change sets between them but what about actually creating change sets well the primary command for that is commit as in commit the working directory create a change set from the current state of the working directory and add that change set to the repo a critical thing to understand about how commits work though is that the working directory itself just like a change set has its own manifest that is a list of all the files which are included in this change set and waiting this change set yet to be so creating new files in our working directory and invoking hg commit is not sufficient to then get those files in a new change set we actually have to explicitly add them to the manifest of the working directory and only then will they be included in the next commit now you don't have to do this for every file included in the commit every single time you make a new commit because the working directory inherits the manifest from its parent change set so you only need to add the files which are new the files which are present now in the working directory that weren't present in the parent change set and lastly in the course of demonstrating these two commands we'll use the log command which lists the change sets of the repo in order of newest first so the log command doesn't actually do anything it doesn't modify anything it's just an informative command it just reports the current set of change sets in your repo so again at the command line notice we're in the slash bar directory so we're in the working directory of the bar repo and we're going to start off by creating a file in that directory called file a with the text content that just reads blah blah and we do so with the command echo which recall is a built in shell command which just echoes its arguments to standard output and then the greater than sign if you don't recall is a shell operator which performs redirection it's redirecting the standard output of the echo command to the file which is called file a which is newly created here if it doesn't already exist and so in the next command if we invoke ls it's listing the content of the bar directory which now has file a notice that .hg is not included in the listing here because we didn't specify the hyphen a option it's still there it's just not being reported in the output of ls here and now having created this new file in the working directory if we try invoking hg commit we get this error message saying nothing changed mercurial won't let us commit unless something has actually changed in our working directory relative to the working directory's parent which is currently the null change set the problem here as I explained is that the manifest for the working directory is currently empty there are no files in it so even though we have an actual file in our working directory as far as mercurial can see it's not supposed to be included in the next commit we can fix this though by invoking hg add and notice here I didn't actually specify which file to add I just wrote hg add and by default hg add will add all files all files which aren't currently already in the manifest so it tells us adding file a it's now there in the manifest and if we invoke hg commit this time it will work though notice when we invoke hg commit we have to specify the option hyphen m followed by a string reading our first commit that hyphen m argument is the message argument when you create a commit mercurial very stringently requires you the committer to actually provide a commit message so that when someone reads through the log they can see what these revisions are supposed to be and obviously our first commit is not a very helpful message but in practice you'd have something more meaningful like here's where we fix this bug or here's where we added a new feature something like that I highly recommend you get in the practice of really coming up with meaningful commit messages in any case so here hg commit succeeds and that creates a new change set consisting of just the file file a and so if we invoke hg log immediately after we can see it's reporting hey there's this one change set it has local revision number zero and its change set ideas starts with digit seven c zero bb eight nine b zero f seven one the tag business will talk about much later ignore that for now user is brand because that's the name I entered in a config file on my system so that's the default username any time I make a commit and mercurial on your system you should enter your name in the config file and then after that of course the date when this change that was created in the time and then the summary that's the commit message so in this example hg log is just printing out one change set because that's all we have we just have the one change set if we had three hundred change sets hg log would print out all three hundred change sets starting with the last one all the way back to the first one so just to be absolutely clear about what a repository now looks like we have this single change set seven z zero bb eight nine b zero f seven one the working directory now has that change set as its parent which itself has the null change set as its parent now when it comes time to remove a file from your working directory if that file was present in the parent change set you shouldn't just simply delete the file because then mercurial will be confused it will expect to find this file in your working directory that isn't there anymore mercurial will consider the file missing so instead of using just the rm command like you normally do in unix to delete files you should use mercurial's remove command which will not only delete the file it will remove it from the working directory's manifest such that it won't be included in the next commit when we demonstrate this will use the status command which shows the status of the working directory that is it shows which files have changed relative to the parent of the working directory which files are set to be added which are set to be removed and also which files are missing that is which files you've deleted without actually removing them from the manifest so resuming our previous example if we start out by using hdremove to remove file a that will remove file a from the manifest of the working directory and also actually delete the file from the working directory and then in the next command we create a new file file b and then next in the invocation of ls the content of the working directory is currently just file b there's no file a because we just removed it and now when we invoke hgstatus it reports that file a denoted with the r is marked for removal and file b is denoted with the question mark because there's no file b in the manifest of the parent change set nor in the manifest of the current working directory so material is not currently tracking this file as we say and now when we make a new commit the new change set doesn't include file a nor does it include file b in fact it's empty there's no files in this manifest so in fact if we now recreate file a and then invoke hgstatus what it reports is that both file a and file b have a question mark because neither is present in the manifest of the working directory nor in the manifest of the parent of the working directory the clear about the hgstatus command that it doesn't list all files in the manifest of the working directory by default it only lists those about which there's something interesting to say like is this file b added has it been modified since the parent is it set to be removed is it a now a missing file denoted with an exclamation mark that is is it a file that according to the working directory manifest is supposed to be there but for some reason is not and question mark again but basically means not tracked it's a file which is there in the working directory but is not being tracked it's not listed in the manifest of the working directory nor in the parent change set of the working directory to copy versions of files stored in the repository into your working directory we have two commands revert and update which serve quite different purposes the revert command is so called because the idea is that you revert some file in your working directory back to its state from an earlier change set and it does this by reconstructing the file from the diffs and snapshots actually stored on the repo and once the version of the file is reconstructed it's then copied into your working directory most commonly the revert command is used to revert a file back to its state from the parent of the working directory so in your working directory you're working on the next version which you're going to commit but you make some mistake you tie yourself up in knots and you decide well I'm just going to scrap these changes and revert this file back to its previous state so that's how revert is most commonly used and if you don't specify any particular revision for a file it will just by default use the revision in the parent but occasionally it is useful you can specify some early revision some other revision and revert the file to that revision instead of necessarily the parent of the working directory the purpose of the update command is quite different with update we're generally bringing the entire working directory in line with some previous revision we're putting everything in the working directory into the state it was in for a specified change set and very importantly the change set to which we are updating that becomes the parent of the working directory in fact this is the primary command by which we specify the parent of the working directory so anytime we want to go back to an old revision and make a branch off of that revision we first do an update then we make our modifications in the working directory and commit and that change set will be a new head effectively a new branch in the repo so for example if this is the state of our repository and we wanted to create a new branch off of the first commit well first we update back to that change set that puts all the files in the working directory back in that first change set and it also modifies the parent of the working directory now to now be that change set if we then do our work in the working directory and make another commit we've effectively created a new head a new branch so going back to our example on the command line remember that we've made a second commit but the manifest of that commit happened to be empty there were no files in it and yet our directory contains these two files file a and file b so hg status reports them with rx meaning that these files aren't being tracked and if we then attempt to update back to the first commit which is local revision 0 here the hyphen r argument stands for revision well the update doesn't complete we get this error saying that there's an untracked file in the working directory which differs from the file in the requested revision and that's file a because if you recall in our first commit we have a file a and so mccurals being cautious because if it's going to update back to that first and it's going to need to clobber that file and because mccurals knows that this file a the one we've newly created isn't in the repository it may represent work that we don't want to lose so mccurals being cautious here but assuming that we know hey we don't care about that file it's okay if we clobber it then we invoke update with the hyphen capital C option which stands for clean but what it really means is to if necessary clobber any files necessary even if those files might have work that has not been committed so the invocation update here reports that one file has been updated that is the file a has been clobbered with the version that existed back in the first change set and if we invoke htstatus then it tells us oh there's just one file now which is present in the working directory but is not in the manifest of the working directory or in the manifest of the parent and the parent again here is the original change set the first change set local revision zero but as the ls command makes clear file a and file b are both in the working directory htstatus doesn't report on file a because file a is currently in the precise same state it was found in the parent in the change set we just updated to so as far as htstatus is concerned there's nothing to report about it now if we then modify file a and we do so here using the echo command but with the append redirection operator which is the two angle brackets that appends blah blah to the end of file a so we've modified file a and then we invoke htstatus and htstatus tells us yes file a is marked with an m because it's been modified since the parent if we decide we want to discard those changes in file a we invoke hgrevert file a and that checks out again it checks out the version of file a found in the parent and then when we invoke htstatus it's not reported anymore because now it's back in the state where it's unchanged since the parent notice that when we invoke revert here we didn't specify which change set we want to revert file a back to so by default it assumes that we mean the parent of the working directory which again is local revision 0 if we wanted some other version of file a we could have specified a different revision with the hyphen r option